python-ly-0.9.3/0000775000175000017500000000000012636745216014646 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ChangeLog0000644000175000017500000000654212636745116016424 0ustar wilbertwilbert00000000000000ChangeLog for python-ly ======================= 2015-12-24: python-ly 0.9.3 - Fix issue #35: do not insert duration after a tie - Added ly.rhythm.music_items() for a more robust way of iterating through chords and notes (awaiting fully fledged editing support through ly.music or ly.xml) - More configurability in ly.colorize, thanks to Urs Liska - MusicXML export various improvements and bug fixes, contributed by Peter Bjuhr 2015-05-14: python-ly 0.9.2 - add the default-language variable to the ly command; this can be set to a language in case a LilyPond document uses a language different than "nederlands" but does not specify it (issue #20). - add the -l, --language option as shorthand for setting the default language - properly support drum notes in ly.lex and ly.music - updated scheme variables in ly.data.scheme* functions for LilyPond 2.18 - fix TypeError: expected string or buffer in dom.ly when string was a dom.Reference (issue wbsoft/frescobaldi#667) - Fix issue #16: Duration after `\skip` may not be removed - MusicXML export improvements: - support for isolated durations (a single duration without explicit pitch) - support for implicit starting pitch in relative mode (issues #18 and wbsoft/frescobaldi#648) 2015-03-08: python-ly 0.9.1 - updated LilyPond data to 2.18 2015-03-07: python-ly 0.9 - added ly.rests containing various rest manipulations - robust Python 3 support, Python 3 is now recommended, although 2.7 will still be supported for the foreseeable future. - don't yield the duration in a \tuplet command as a music token (issue wbsoft/frescobaldi#631) - a script `xml-export.ily` has been included to dump the music structure inside LilyPond to an XML file. This is not used yet, but could be used in the future to use LilyPond to parse files and build music, and then export it to other formats. - MusicXML export improvements 2015-01-24: python-ly 0.8 - fix (allbeit experimental) musicxml export - basic api documentation included 2015-01-23: python-ly 0.7 - first release as an officially separate project from Frescobaldi - add INSTALL.md to source distribution 2015-01-23: python-ly 0.6 - node and slexer are no longer toplevel modules; only the ly package 2015-01-21: python-ly 0.5 - large MusicXML export improvements, contributed by Peter Bjuhr (MusicXML export is still experimental) - handle german pitch names asas and heses correctly when writing those - don't transpose chord argument of \stringTuning command - Python3 robustness improvements 2014-03-05: python-ly 0.4 - fix transposing when alterations would be more than a double sharp or double flat; handle it by moving the note, just like LilyPond does it - Python3 installation fixes - small MusicXML export improvements, contributed by Peter Bjuhr (MusicXML export is still very experimental) 2014-02-05: python-ly 0.3 - new command 'highlight' ('hl') to create syntax-highlighted HTML files of LilyPond source files (or any file that is understood by ly.lex) - new, very experimental, command 'musicxml' to export music to MusicXML 2014-01-08: python-ly 0.2 - new commands 'abs2rel' and 'rel2abs' that convert \relative music to absolute and vice versa - support for Python 3. Not all of the ly python module has already been tested, but installing and running the various ly commands works well. 2014-01-07: python-ly 0.1 - initial release python-ly-0.9.3/ly/0000775000175000017500000000000012636745216015272 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/reformat.py0000644000175000017500000000757212461001227017453 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Formatting tools to improve the readability of a ly.document.Document without changing the semantic meaning of the LilyPond source. Basically the tools only change whitespace to make the source-code more readable. See also ly.indent. """ from __future__ import unicode_literals import ly.indent import ly.lex def break_indenters(cursor): """Add newlines around indent and dedent tokens where needed. If there is stuff after a { or << (that's not closed on the same line) it is put on a new line, and if there if stuff before a } or >>, the } or >> is put on a new line. It is necessary to run the indenter again over the same part of the document, as it will look garbled with the added newlines. """ with cursor.document as d: for b in cursor.blocks(): denters = [] tokens = d.tokens(b) nonspace_index = -1 for i, t in enumerate(tokens): if isinstance(t, ly.lex.Indent) and t in ('{', '<<'): denters.append(i) elif isinstance(t, ly.lex.Dedent) and t in ('}', '>>'): if denters: denters.pop() elif nonspace_index != -1: # add newline before t pos = d.position(b) + t.pos d[pos:pos] = '\n' if not isinstance(t, ly.lex.Space): nonspace_index = i for i in denters: if i < nonspace_index: # add newline after tokens[i] pos = d.position(b) + tokens[i].end d[pos:pos] = '\n' def move_long_comments(cursor): """Move line comments with more than 2 comment characters to column 0.""" with cursor.document as d: for b in cursor.blocks(): tokens = d.tokens(b) if (len(tokens) == 2 and isinstance(tokens[0], ly.lex.Space) and isinstance(tokens[1], ( ly.lex.lilypond.LineComment, ly.lex.scheme.LineComment)) and tokens[1][:3] in ('%%%', ';;;')): del d[d.position(b):d.position(b) + tokens[1].pos] def remove_trailing_whitespace(cursor): """Removes whitespace from all lines in the cursor's range.""" with cursor.document as d: for b in cursor.blocks(): tokens = d.tokens(b) if tokens: t = tokens[-1] end = d.position(b) + t.end if isinstance(t, ly.lex.Space): del d[end-len(t):end] elif not isinstance(t, ly.lex.String): offset = len(t) - len(t.rstrip()) if offset: del d[end-offset:end] def reformat(cursor, indenter): """A do-it-all function improving the LilyPond source formatting.""" break_indenters(cursor) indenter.indent(cursor) move_long_comments(cursor) remove_trailing_whitespace(cursor) python-ly-0.9.3/ly/__init__.py0000644000175000017500000000466512474152537017412 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ A package of modules dealing with LilyPond and the LilyPond format. The ly module supports both Python2 and Python3. This is a short description of some modules: * ly.slexer: generic tools to build parsers using regular expressions * ly.node: a generic list-like node object to build tree structures with * ly.document: a tokenized text document (LilyPond file) * ly.docinfo: harvests and caches various information from a LilyPond document * ly.lex: a parser for LilyPond, Scheme, and other formats, using slexer * ly.music: a tree structure of the contents of a document * ly.pitch: functions for translating, transposing etc * ly.rhythm: functions dealing with rhythm * ly.indent: indent LilyPond text * ly.reformat: format LilyPond text * ly.dom: (deprecated) tree structure to build LilyPond text from * ly.words: words for highlighting and autocompletion * ly.data: layout objects, properties, interfaces, font glyphs etc extracted from LilyPond * ly.cli: the implementation of the command-line 'ly' script A LilyPond document (source file) is held by a ly.document.Document. The Document class automatically parses and tokenizes the text, also when its contents are changed. A music tree can be built from a document using the ly.music module. In the near future, music trees can be built from scratch and also generate LilyPond output from scratch. At that moment, ly.dom is deprecated. The functions in ly.pitch, such as transpose and translate currently access the tokens in the document, but in the future they will work on the music tree. """ python-ly-0.9.3/ly/rests.py0000644000175000017500000000642312476157645017014 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2011 - 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. """ Implementation of tools to edit rests of selected music. All functions except a ly.document.Cursor with the selected range. """ from __future__ import unicode_literals import ly.document import ly.lex.lilypond def replace_rest(cursor, replace_token): """Replace full rests (r) with optional token. """ source = ly.document.Source(cursor, True, tokens_with_position=True) with cursor.document as d: for token in source: if isinstance(token, ly.lex.lilypond.Rest): if token == 'r': d[token.pos:token.end] = replace_token def replace_fmrest(cursor, replace_token): """Replace full measure rests (R) with optional token. """ source = ly.document.Source(cursor, True, tokens_with_position=True) with cursor.document as d: for token in source: if isinstance(token, ly.lex.lilypond.Rest): if token == 'R': d[token.pos:token.end] = replace_token def replace_spacer(cursor, replace_token): """Replace spacer rests (s) with optional token. """ source = ly.document.Source(cursor, True, tokens_with_position=True) with cursor.document as d: for token in source: if isinstance(token, ly.lex.lilypond.Spacer): d[token.pos:token.end] = replace_token def replace_restcomm(cursor, replace_token): r"""Replace rests by rest command (\rest) with optional token. """ def get_comm_rests(source): r"""Catch all rests by rest command (\rest) from source.""" rest_tokens = None for token in source: if isinstance(token, ly.lex.lilypond.Note): rest_tokens = [token] continue if rest_tokens and isinstance(token, ly.lex.Space): rest_tokens.append(token) continue if rest_tokens and isinstance(token, ly.lex.lilypond.Command): if token == '\\rest': rest_tokens.append(token) yield rest_tokens rest_tokens = None source = ly.document.Source(cursor, True, tokens_with_position=True) with cursor.document as d: for rt in get_comm_rests(source): note = rt[0] space = rt[-2] comm = rt[-1] d[note.pos:note.end] = replace_token del d[space.pos:space.end] del d[comm.pos:comm.end] python-ly-0.9.3/ly/barcheck.py0000644000175000017500000001262612461001227017372 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Add, check or remove bar checks in selected music. """ from __future__ import unicode_literals from __future__ import print_function import collections import itertools import ly.document import ly.lex.lilypond def remove(cursor): """Remove bar checks from the selected music.""" s = ly.document.Source(cursor, tokens_with_position=True) prv, cur = None, None with cursor.document as d: for nxt in itertools.chain(s, (None,)): if isinstance(cur, ly.lex.lilypond.PipeSymbol): if isinstance(prv, ly.lex.Space): # pipesymbol and adjacent space may be deleted if nxt == '\n': del d[prv.pos:cur.end] elif isinstance(nxt, ly.lex.Space): del d[cur.pos:nxt.end] else: del d[cur.pos:cur.end] elif isinstance(nxt, ly.lex.Space): # delete if followed by a space del d[cur.pos:cur.end] else: # replace "|" with a space d[cur.pos:cur.end] = " " prv, cur = cur, nxt class event(object): """A limited event type at a certain time.""" def __init__(self): self._nodes = [] self.cadenza = None self.barcheck = False self.timesig = None self.partial = None def append(self, node): self._nodes.append(node) def __repr__(self): s = [] if self.cadenza is not None: s.append('cadenza' + ('On' if self.cadenza else 'Off')) if self.barcheck: s.append('bar') if self.timesig is not None: s.append('T{0}'.format(self.timesig)) if self.partial is not None: s.append('P{0}'.format(self.partial)) if self._nodes: s.append(repr(self._nodes)) return ''.format(' '.join(s)) def insert(cursor, music=None): """Insert bar checks within the selected range.""" if music is None: import ly.music music = ly.music.document(cursor.document) if len(music) == 0: return if cursor.start: n = music.node(cursor.start, 1) nodes = itertools.chain((n,), n.forward()) else: nodes = music if cursor.end is None: iter_nodes = iter else: predicate = lambda node: node.position < cursor.end def iter_nodes(it): return itertools.takewhile(predicate, it) # make time-based lists of events event_lists = [] def do_topnode(node): if not isinstance(node, ly.music.items.Music): for n in node: do_topnode(n) return def do_node(node, time, scaling): if isinstance(node, (ly.music.items.Durable, ly.music.items.UserCommand)): if node.position >= cursor.start: events[time].append(node) time += node.length() * scaling elif isinstance(node, ly.music.items.TimeSignature): events[time].timesig = node.measure_length() elif isinstance(node, ly.music.items.Partial): events[time].partial = node.length() elif isinstance(node, ly.music.items.PipeSymbol): events[time].barcheck = True elif isinstance(node, ly.music.items.Command) and node.token in ( 'cadenzaOn', 'cadenzaOff'): events[time].cadenza = node.token == 'cadenzaOn' elif isinstance(node, ly.music.items.Grace): pass elif isinstance(node, ly.music.items.LyricMode): pass elif isinstance(node, ly.music.items.MusicList) and node.simultaneous: time = max(do_node(n, time, scaling) for n in iter_nodes(node)) elif isinstance(node, ly.music.items.Music): if isinstance(node, ly.music.items.Scaler): scaling *= node.scaling for n in iter_nodes(node): time = do_node(n, time, scaling) else: do_topnode(node) return time events = collections.defaultdict(event) do_node(node, 0, 1) event_lists.append(sorted(events.items())) do_topnode(nodes) for event_list in event_lists: # default to 4/4 without pickup measure_length = 1 measure_pos = 0 for time, evt in event_list: print(time, evt) python-ly-0.9.3/ly/util.py0000644000175000017500000000724312461641363016617 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Utility functions. """ from __future__ import unicode_literals import string _nums = ( '', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen') _tens = ( 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety') def int2text(number): """Converts an integer to the English language name of that integer. E.g. converts 1 to "One". Supports numbers 0 to 999999. This can be used in LilyPond identifiers (that do not support digits). """ result = [] if number >= 1000: hundreds, number = divmod(number, 1000) result.append(int2text(hundreds) + "Thousand") if number >= 100: tens, number = divmod(number, 100) result.append(_nums[tens] + "Hundred") if number < 20: result.append(_nums[number]) else: tens, number = divmod(number, 10) result.append(_tens[tens-2] + _nums[number]) text = "".join(result) return text or 'Zero' # Thanks: http://billmill.org/python_roman.html _roman_numerals = (("M", 1000), ("CM", 900), ("D", 500), ("CD", 400), ("C", 100), ("XC", 90), ("L", 50), ("XL", 40), ("X", 10), ("IX", 9), ("V", 5), ("IV", 4), ("I", 1)) def int2roman(n): """Convert an integer value to a roman number string. E.g. 1 -> "I", 12 -> "XII", 2015 -> "MMXV" n has to be > 1. """ if n < 1: raise ValueError('Roman numerals must be positive integers, got %s' % n) roman = [] for ltr, num in _roman_numerals: k, n = divmod(n, num) roman.append(ltr * k) return "".join(roman) def int2letter(number, chars=string.ascii_uppercase): """Converts an integer to one or more letters. E.g. 1 -> A, 2 -> B, ... 26 -> Z, 27 -> AA, etc. Zero returns the empty string. chars is the string to pick characters from, defaulting to string.ascii_uppercase. """ mod = len(chars) result = [] while number > 0: number, c = divmod(number - 1, mod) result.append(c) return "".join(chars[c] for c in reversed(result)) def mkid(*args): """Makes a lower-camel-case identifier of the strings in args. All strings are concatenated with the first character of every string uppercased, except for the first character, which is lowercased. Examples:: mkid("Violin") ==> "violin" mkid("soprano", "verse") ==> "sopranoVerse" mkid("scoreOne", "choirII") ==> "scoreOneChoirII" """ result = [] for a in args[:1]: result.append(a[:1].lower()) result.append(a[1:]) for a in args[1:]: result.append(a[:1].upper()) result.append(a[1:]) return "".join(result) python-ly-0.9.3/ly/music/0000775000175000017500000000000012636745216016412 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/music/__init__.py0000644000175000017500000000632212461001227020503 0ustar wilbertwilbert00000000000000# 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. r""" An api to read music from the tokens of a ly.document.Document into a tree structure. This is meant to quickly read music from a document, to perform modifications on the document, and to interpret music and markup and to convert or export it to other formats. All nodes are a subclass of items.Item, (which inherits from node.WeakNode). Tree structures are created from nested LilyPond structures, markup and scheme code. Some Item types have special methods to query information. The Music type, for example, has a length() method that returns the duration of the music fragment. Using the Music.events() method and the events module, it is possible to iterate in musical time over the music tree, e.g. to convert music to another format. This package is not yet capable to construct documents entirely from scratch. This needs to be developed. Until that time, the ly.dom module can be used instead. Some Item types can have a list of child items, but the tree structure is as linear as possible. A convenience function is available to create a ly.music.items.Document instance for the specified ly.document.Document. Here is an example:: >>> import ly.document >>> import ly.music >>> d=ly.document.Document(r''' \version "2.18.0" music = \relative { \time 4/4 \key d \minor d4 e f g a g f e d2 } \score { \new Staff << \music >> } ''') >>> m=ly.music.document(d) >>> print m.dump() >>> m[2][0][0] >>> m[2][0][0].length() Fraction(5, 2) >>> """ import ly.document def document(doc): """Return a music.items.Document instance for the ly.document.Document.""" from . import items return items.Document(doc) python-ly-0.9.3/ly/music/read.py0000644000175000017500000011353012477577712017707 0ustar wilbertwilbert00000000000000# 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 Reader is used to construct a tree structure of (musical and other) items in a LilyPond document. The item types that are used are in the items module. You instantiate a Reader with a ly.document.Source that reads tokens from any (part of a) ly.document.Document. 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. """ from __future__ import unicode_literals from __future__ import division import itertools from fractions import Fraction import ly.duration import ly.pitch from ly import lex from ly.lex import lilypond from ly.lex import scheme from .items import * def skip(source, what=(lex.Space, lex.Comment)): """Yield tokens from source, skipping items of classes specified in what. By default, comments and whitespace are skipped. """ for t in source: if not isinstance(t, what): yield t class dispatcher(object): """Decorator creator to dispatch commands, keywords, etc. to a method.""" def __init__(self): self.d = {} def read_arg(self, a): return a def __call__(self, *args): d = self.d def wrapper(func): for a in args: d[a] = func doc = "handle " + ", ".join(map(self.read_arg, args)) func.__doc__ = doc if not func.__doc__ else func.__doc__ + '\n\n' + doc return func return wrapper def method(self, token): """The registered method to call for the token. May return None.""" return self.d.get(token) class dispatcher_class(dispatcher): """Decorator creator to dispatch the class of a token to a method.""" def read_arg(self, a): return a.__name__ def method(self, token): """The registered method to call for the token's class. May return None.""" cls = token.__class__ d = self.d try: return d[cls] except KeyError: for c in cls.__mro__[1:]: try: meth = d[cls] = d[c] except KeyError: if c is not lex.Token: continue meth = d[cls] = None return meth class Reader(object): """Reads tokens from a Source and builds a meaningful tree structure.""" _commands = dispatcher() _keywords = dispatcher() _tokencls = dispatcher_class() _markup = dispatcher_class() _scheme = dispatcher_class() def __init__(self, source): """Initialize with a ly.document.Source. The language is set to "nederlands". """ self.source = source self.language = "nederlands" self.in_chord = False self.prev_duration = Fraction(1, 4), 1 def set_language(self, lang): r"""Changes the pitch name language to use. Called internally when \language or \include tokens are encountered with a valid language name/file. Sets the language attribute to the language name. """ if lang in ly.pitch.pitchInfo: self.language = lang return True def add_duration(self, item, token=None, source=None): """Add a duration attribute to the item. When there are tokens, a Duration item is also appended to the item. """ source = source or self.source tokens = [] if not token or isinstance(token, lilypond.Duration): if token: tokens.append(token) for token in source: if isinstance(token, lilypond.Duration): if tokens and isinstance(token, lilypond.Length): self.source.pushback() break tokens.append(token) elif not isinstance(token, lex.Space): self.source.pushback() break if tokens: d = self.factory(Duration, tokens[0]) d.tokens = tuple(tokens[1:]) item.append(d) item.duration = self.prev_duration = ly.duration.base_scaling(tokens) else: item.duration = self.prev_duration def consume(self, last_token=None): """Yield the tokens from our source until a parser is exit. If last_token is given, it is called with the last token that is read. """ t = None for t in self.source.until_parser_end(): yield t if last_token and t is not None: last_token(t) def factory(self, cls, token=None, consume=False, position=None): """Create Item instance for token. If consume is True, consume()s the source into item.tokens. If you don't specify a token, you must specify the position (>= 0). """ item = cls() item.document = self.source.document if token: item.token = token item.position = token.pos elif position is None: raise ValueError("position must be specified if no token") else: item.position = position if consume: item.tokens = tuple(self.consume()) if not token and item.tokens: item.position = item.tokens[0].pos return item def add_bracketed(self, item, source): """Append the arguments between brackets to the item. Returns True if that succeeded, else False. """ for t in source: if isinstance(t, lilypond.OpenBracket): tokens = [t] item.extend(self.read(self.consume(tokens.append))) item.tokens = tuple(tokens) return True elif not isinstance(t, lex.Space): self.source.pushback() break return False def read(self, source=None): """Yield Item instances reading from source.""" source = source or self.source for t in skip(source): item = self.read_item(t, source) if item: yield item def read_item(self, t, source=None): """Return one Item that starts with token t. May return None.""" meth = self._tokencls.method(t) if meth: return meth(self, t, source or self.source) @_tokencls(lilypond.SchemeStart) @_markup(lilypond.SchemeStart) def handle_scheme_start(self, t, source=None): return self.read_scheme_item(t) @_tokencls(lex.StringStart) @_markup(lex.StringStart) @_scheme(lex.StringStart) def handle_string_start(self, t, source=None): return self.factory(String, t, True) @_tokencls( lilypond.DecimalValue, lilypond.IntegerValue, lilypond.Fraction, ) def handle_number_class(self, t, source=None): return self.factory(Number, t) @_tokencls(lilypond.MusicItem) def handle_music_item(self, t, source): return self.read_music_item(t, source) @_tokencls(lilypond.Length) def handle_length(self, t, source): item = self.factory(Unpitched, position=t.pos) self.add_duration(item, t, source) return item @_tokencls(lilypond.ChordStart) def handle_chord_start(self, t, source): if not self.in_chord: self.in_chord = True chord = self.factory(Chord, t) def last(t): chord.tokens += (t,) chord.extend(self.read(self.consume(last))) self.in_chord = False self.add_duration(chord, None, source) return chord @_tokencls( lilypond.OpenBracket, lilypond.OpenSimultaneous, lilypond.SimultaneousOrSequentialCommand, ) def handle_music_list(self, t, source): item, it = self.test_music_list(t) if item: if it: item.extend(self.read(it)) return item @_tokencls(lilypond.Command) def read_command(self, t, source): """Read the rest of a command given in t from the source.""" meth = self._commands.method(t) if meth: return meth(self, t, source) return self.factory(Command, t) @_tokencls(lilypond.Keyword) def read_keyword(self, t, source): """Read the rest of a keyword given in t from the source.""" meth = self._keywords.method(t) if meth: return meth(self, t, source) return self.factory(Keyword, t) @_tokencls(lilypond.UserCommand) def read_user_command(self, t, source): """Read a user command, this can be a variable reference.""" return self.factory(UserCommand, t) @_tokencls(lilypond.ChordSeparator) def read_chord_specifier(self, t, source=None): """Read stuff behind notes in chordmode.""" item = self.factory(ChordSpecifier, position=t.pos) item.append(self.factory(ChordItem, t)) for t in self.consume(): if isinstance(t, lilypond.ChordItem): item.append(self.factory(ChordItem, t)) elif isinstance(t, lilypond.Note): r = ly.pitch.pitchReader(self.language)(t) if r: note = self.factory(Note, t) note.pitch = ly.pitch.Pitch(*r) item.append(note) return item @_tokencls(lilypond.TremoloColon) def read_tremolo(self, t, source=None): """Read a tremolo.""" item = self.factory(Tremolo, t) for t in self.source: if isinstance(t, lilypond.TremoloDuration): item.append(self.factory(Duration, t)) item.duration = ly.duration.base_scaling_string(t) else: self.source.pushback() break return item @_tokencls(lilypond.Name, lilypond.ContextProperty) def handle_name(self, t, source): return self.read_assignment(t) @_tokencls( lilypond.PaperVariable, lilypond.LayoutVariable, lilypond.HeaderVariable, lilypond.UserVariable, ) def handle_variable_assignment(self, t, source): item = self.read_assignment(t) if item: # handle \pt, \in etc. for t in skip(self.source): if isinstance(t, lilypond.Unit): item.append(self.factory(Command, t)) else: self.source.pushback() break return item _direct_items = { lilypond.VoiceSeparator: VoiceSeparator, lilypond.PipeSymbol: PipeSymbol, lilypond.Dynamic: Dynamic, lilypond.Tie: Tie, } @_tokencls(*_direct_items) def handle_direct_items(self, t, source): """Tokens that directly translate to an Item.""" return self.factory(self._direct_items[t.__class__], t) @_tokencls(lilypond.Direction) def handle_direction(self, t, source): item = self.factory(Postfix, t) item.direction = '_-^'.index(t) - 1 for t in skip(source): if isinstance(t, ( lex.StringStart, lilypond.MarkupStart, lilypond.Articulation, lilypond.Slur, lilypond.Beam, lilypond.Dynamic, )): item.append(self.read_item(t)) elif isinstance(t, lilypond.Command) and t in ('\\tag'): item.append(self.read_item(t)) elif isinstance(t, lilypond.Keyword) and t in ('\\tweak'): item.append(self.read_item(t)) else: self.source.pushback() break return item @_tokencls(lilypond.Slur) def handle_slurs(self, t, source=None): cls = PhrasingSlur if t.startswith('\\') else Slur item = self.factory(cls, t) item.event = 'start' if t.endswith('(') else 'stop' return item @_tokencls(lilypond.Beam) def handle_beam(self, t, source=None): item = self.factory(Beam, t) item.event = 'start' if t == '[' else 'stop' return item @_tokencls(lilypond.Articulation) def handle_articulation(self, t, source=None): return self.factory(Articulation, t) def read_assignment(self, t): """Read an assignment from the variable name. May return None.""" item = self.factory(Assignment, t) for t in skip(self.source): if isinstance(t, (lilypond.Variable, lilypond.UserVariable, lilypond.DotPath)): item.append(self.factory(PathItem, t)) elif isinstance(t, lilypond.EqualSign): item.tokens = (t,) for i in self.read(): item.append(i) break return item elif isinstance(t, lilypond.SchemeStart): # accept only one scheme item, if another one is found, # return the first, and discard the Assignment item # (should not normally happen) for s in item.find(Scheme): self.source.pushback() return s item.append(self.read_scheme_item(t)) else: self.source.pushback() return def test_music_list(self, t): r"""Test whether a music list ({ ... }, << ... >>, starts here. Also handles \simultaneous { ... } and \sequential { ... } correctly. These obscure commands are not even highlighted by lex, but they exist in LilyPond... \simultaneous { ... } is like << ... >> but \sequential << ... >> just behaves like << ... >> Returns a two-tuple(item; iterable), both may be None. If item is not None, it can be either a UserCommand or a MusicList. If iterable is None, the item is a UserCommand (namely \simultaneous or \sequential, but not followed by a { or <<); else the item is a MusicList, and the iterable should be read fully to get all the tokens inside the MusicList. If item is None, there is no MusicList and no token is read. This way you can handle the { ... } and << ... >> transparently in every input mode. """ def make_music_list(t, simultaneous, tokens=()): """Make the MusicList item.""" item = self.factory(MusicList, t) item.simultaneous = simultaneous item.tokens = tokens def last(t): item.tokens += (t,) return item, self.consume(last) if isinstance(t, (lilypond.OpenBracket, lilypond.OpenSimultaneous)): return make_music_list(t, t == '<<') elif isinstance(t, lilypond.SimultaneousOrSequentialCommand): for t1 in skip(self.source): if isinstance(t1, (lilypond.OpenBracket, lilypond.OpenSimultaneous)): return make_music_list(t, t == '\\simultaneous' or t1 == '<<', (t1,)) else: self.source.pushback() return self.factory(Keyword, t), None return None, None def read_music_item(self, t, source): r"""Read one music item (note, rest, s, \skip, or q) from t and source.""" item = None in_pitch_command = isinstance(self.source.state.parser(), lilypond.ParsePitchCommand) if t.__class__ == lilypond.Note: r = ly.pitch.pitchReader(self.language)(t) if r: item = self.factory(Note, t) p = item.pitch = ly.pitch.Pitch(*r) for t in source: if isinstance(t, lilypond.Octave): p.octave = ly.pitch.octaveToNum(t) item.octave_token = t elif isinstance(t, lilypond.Accidental): item.accidental_token = p.accidental = t elif isinstance(t, lilypond.OctaveCheck): p.octavecheck = ly.pitch.octaveToNum(t) item.octavecheck_token = t break elif not isinstance(t, lex.Space): self.source.pushback() break else: cls = { lilypond.Rest: Rest, lilypond.Skip: Skip, lilypond.Spacer: Skip, lilypond.Q: Q, lilypond.DrumNote: DrumNote, }[t.__class__] item = self.factory(cls, t) if item: if not self.in_chord and not in_pitch_command: self.add_duration(item, None, source) return item @_commands('\\relative') def handle_relative(self, t, source): item = self.factory(Relative, t) # get one pitch and exit on a non-comment pitch_found = False for i in self.read(source): item.append(i) if not pitch_found and isinstance(i, Note): pitch_found = True continue break return item @_commands('\\absolute') def handle_absolute(self, t, source): item = self.factory(Absolute, t) for i in self.read(source): item.append(i) break return item @_commands('\\transpose') def handle_transpose(self, t, source): item = self.factory(Transpose, t) # get two pitches pitches_found = 0 for i in self.read(source): item.append(i) if pitches_found < 2 and isinstance(i, Note): pitches_found += 1 continue break return item @_commands('\\clef') def handle_clef(self, t, source): item = self.factory(Clef, t) for t in skip(source): if isinstance(t, lilypond.ClefSpecifier): item._specifier = t elif isinstance(t, lex.StringStart): item._specifier = self.factory(String, t, True) break return item @_commands('\\key') def handle_key(self, t, source): item = self.factory(KeySignature, t) item.extend(itertools.islice(self.read(source), 2)) return item @_commands('\\times', '\\tuplet', '\\scaleDurations') def handle_scaler(self, t, source): item = self.factory(Scaler, t) item.scaling = 1 if t == '\\scaleDurations': for i in self.read(source): item.append(i) if isinstance(i, Number): item.scaling = i.value() elif isinstance(i, Scheme): try: pair = i.get_pair_ints() if pair: item.scaling = Fraction(*pair) else: val = i.get_fraction() if val is not None: item.scaling = val except ZeroDivisionError: pass break elif t == '\\tuplet': for t in source: if isinstance(t, lilypond.Fraction): item.append(self.factory(Number, t)) item.numerator, item.denominator = map(int, t.split('/')) item.scaling = 1 / Fraction(t) elif isinstance(t, lilypond.Duration): self.add_duration(item, t, source) break elif not isinstance(t, lex.Space): self.source.pushback() break else: # t == '\\times' for t in source: if isinstance(t, lilypond.Fraction): item.append(self.factory(Number, t)) item.numerator, item.denominator = map(int, t.split('/')) item.scaling = Fraction(t) break elif not isinstance(t, lex.Space): self.source.pushback() break for i in self.read(source): item.append(i) break return item @_commands('\\tag', '\\keepWithTag', '\\removeWithTag', '\\appendToTag', '\\pushToTag') def handle_tag(self, t, source): item = self.factory(Tag, t) argcount = 3 if t in ('\\appendToTag', '\\pushToTag') else 2 item.extend(itertools.islice(self.read(), argcount)) return item @_commands('\\grace', '\\acciaccatura', '\\appoggiatura', '\\slashedGrace') def handle_grace(self, t, source): item = self.factory(Grace, t) for i in self.read(source): item.append(i) break return item @_commands('\\afterGrace') def handle_after_grace(self, t, source): item = self.factory(AfterGrace, t) for i in itertools.islice(self.read(source), 2): item.append(i) # put the grace music in a Grace item if len(item) > 1: i = self.factory(Grace, position=item[-1].position) i.append(item[-1]) item.append(i) return item @_commands('\\repeat') def handle_repeat(self, t, source): item = self.factory(Repeat, t) item._specifier = None item._repeat_count = None for t in skip(source): if isinstance(t, lilypond.RepeatSpecifier): item._specifier = t elif not item.specifier and isinstance(t, lex.StringStart): item._specifier = self.factory(String, t, True) elif isinstance(t, lilypond.RepeatCount): item._repeat_count = t elif isinstance(t, lilypond.SchemeStart): # the specifier or count may be specified using scheme s = self.read_scheme_item(t) if item._specifier: if item._repeat_count: item.append(s) break item._repeat_count = s else: item._specifier = s else: self.source.pushback() for i in self.read(source): item.append(i) break for t in skip(source): if t == '\\alternative' and isinstance(t, lilypond.Command): item.append(self.handle_alternative(t, source)) else: self.source.pushback() break break return item @_commands('\\alternative') def handle_alternative(self, t, source): item = self.factory(Alternative, t) for i in self.read(source): item.append(i) break return item @_commands('\\tempo') def handle_tempo(self, t, source): item = self.factory(Tempo, t) source = self.consume() equal_sign_seen = False text_seen = False t = None for t in source: if not equal_sign_seen: if (not text_seen and isinstance(t, (lilypond.SchemeStart, lex.StringStart, lilypond.Markup))): item.append(self.read_item(t)) t = None text_seen = True elif isinstance(t, lilypond.Length): self.add_duration(item, t, source) t = None elif isinstance(t, lilypond.EqualSign): item.tokens = (t,) equal_sign_seen = True t = None elif isinstance(t, (lilypond.IntegerValue, lilypond.SchemeStart)): item.append(self.read_item(t)) elif t == "-": item.tokens += (t,) ## if the last token does not belong to the \\tempo expression anymore, ## push it back if t and not isinstance(t, (lex.Space, lex.Comment)): self.source.pushback() return item @_commands('\\time') def handle_time(self, t, source): item = self.factory(TimeSignature, t) for t in skip(source): if isinstance(t, lilypond.SchemeStart): item._beatstructure = self.read_scheme_item(t) continue elif isinstance(t, lilypond.Fraction): item._num, den = map(int, t.split('/')) item._fraction = Fraction(1, den) else: self.source.pushback() break return item @_commands('\\partial') def handle_partial(self, t, source): item = self.factory(Partial, t) self.add_duration(item, None, source) return item @_commands('\\new', '\\context', '\\change') def handle_translator(self, t, source): cls = Change if t == '\\change' else Context item = self.factory(cls, t) isource = self.consume() for t in skip(isource): if isinstance(t, (lilypond.ContextName, lilypond.Name)): item._context = t for t in isource: if isinstance(t, lilypond.EqualSign): for t in isource: if isinstance(t, lex.StringStart): item._context_id = self.factory(String, t, True) break elif isinstance(t, lilypond.Name): item._context_id = t break elif not isinstance(t, lex.Space): self.source.pushback() break elif not isinstance(t, lex.Space): self.source.pushback() break else: self.source.pushback() break if cls is not Change: for i in self.read(source): item.append(i) if not isinstance(i, With): break return item _inputmode_commands = { '\\notemode': NoteMode, '\\notes': NoteMode, '\\chordmode': ChordMode, '\\chords': ChordMode, '\\figuremode': FigureMode, '\\figures': FigureMode, '\\drummode': DrumMode, '\\drums': DrumMode, } @_commands(*_inputmode_commands) def handle_inputmode(self, t, source): cls = self._inputmode_commands[t] item = self.factory(cls, t) for i in self.read(): item.append(i) break return item _lyricmode_commands = { '\\lyricmode': LyricMode, '\\lyrics': LyricMode, '\\oldaddlyrics': LyricMode, '\\addlyrics': LyricMode, '\\lyricsto': LyricsTo, } @_commands(*_lyricmode_commands) def handle_lyricmode(self, t, source): cls = self._lyricmode_commands[t] item = self.factory(cls, t) if cls is LyricsTo: for t in skip(source): if isinstance(t, lilypond.Name): item._context_id = t elif isinstance(t, (lex.String, lilypond.SchemeStart)): item._context_id = self.read_item(t) else: self.source.pushback() break for t in skip(self.consume()): i = self.read_lyric_item(t) or self.read_item(t) if i: item.append(i) break return item def read_lyric_item(self, t): """Read one lyric item. Returns None for tokens it does not handle.""" if isinstance(t, (lex.StringStart, lilypond.MarkupStart)): item = self.factory(LyricText, position=t.pos) item.append(self.read_item(t)) self.add_duration(item) return item elif isinstance(t, lilypond.LyricText): item = self.factory(LyricText, t) self.add_duration(item) return item elif isinstance(t, lilypond.Lyric): return self.factory(LyricItem, t) else: item, source = self.test_music_list(t) if item: if source: for t in skip(source): i = self.read_lyric_item(t) or self.read_item(t) if i: item.append(i) return item @_commands('\\stringTuning') def handle_string_tuning(self, t, source): item = self.factory(StringTuning, t) for arg in self.read(source): item.append(arg) break return item @_commands('\\partcombine') def handle_partcombine(self, t, source=None): item = self.factory(PartCombine, t) item.extend(itertools.islice(self.read(), 2)) return item @_keywords('\\language') def handle_language(self, t, source): item = self.factory(Language, t) for name in self.read(source): item.append(name) if isinstance(name, String): value = item.language = name.value() if value in ly.pitch.pitchInfo: self.language = value break return item @_keywords('\\include') def handle_include(self, t, source): item = None name = None for name in self.read(source): if isinstance(name, String): value = name.value() if value.endswith('.ly') and value[:-3] in ly.pitch.pitchInfo: item = self.factory(Language, t) item.language = self.language = value[:-3] item.append(name) break if not item: item = self.factory(Include, t) if name: item.append(name) return item @_keywords('\\version') def handle_version(self, t, source): item = self.factory(Version, t) for arg in self.read(source): item.append(arg) break return item _bracketed_keywords = { '\\header': Header, '\\score': Score, '\\bookpart': BookPart, '\\book': Book, '\\paper': Paper, '\\layout': Layout, '\\midi': Midi, '\\with': With, '\\context': LayoutContext, } @_keywords(*_bracketed_keywords) def handle_bracketed(self, t, source): cls = self._bracketed_keywords[t] item = self.factory(cls, t) if not self.add_bracketed(item, source) and t == '\\with': # \with also supports one other argument instead of { ... } for i in self.read(source): item.append(i) break return item @_keywords('\\set') def handle_set(self, t, source): item = self.factory(Set, t) tokens = [] for t in skip(source): tokens.append(t) if isinstance(t, lilypond.EqualSign): item.tokens = tuple(tokens) for i in self.read(source): item.append(i) break break return item @_keywords('\\unset') def handle_unset(self, t, source): item = self.factory(Unset, t) tokens = [] for t in skip(self.consume()): if type(t) not in lilypond.ParseUnset.items: self.source.pushback() break tokens.append(t) item.tokens = tuple(tokens) return item @_keywords('\\override') def handle_override(self, t, source): item = self.factory(Override, t) for t in skip(self.consume()): if isinstance(t, (lex.StringStart, lilypond.SchemeStart)): item.append(self.read_item(t)) elif isinstance(t, lilypond.EqualSign): item.tokens = (t,) for i in self.read(): item.append(i) break break else: item.append(self.factory(PathItem, t)) return item @_keywords('\\revert') def handle_revert(self, t, source): item = self.factory(Revert, t) t = None for t in skip(self.consume()): if type(t) in lilypond.ParseRevert.items: item.append(self.factory(PathItem, t)) else: break if isinstance(t, lilypond.SchemeStart) and not any( isinstance(i.token, lilypond.GrobProperty) for i in item): item.append(self.read_scheme_item(t)) else: self.source.pushback() return item @_keywords('\\tweak') def handle_tweak(self, t, source): item = self.factory(Tweak, t) t = None for t in skip(self.consume()): if type(t) in lilypond.ParseTweak.items: item.append(self.factory(PathItem, t)) else: self.source.pushback() break if len(item) == 0 and isinstance(t, lilypond.SchemeStart): item.append(self.read_scheme_item(t)) for i in self.read(): item.append(i) break return item @_commands('\\markup', '\\markuplist', '\\markuplines') def handle_markup(self, t, source=None): item = self.factory(Markup, t) self.add_markup_arguments(item) return item def read_markup(self, t): """Read LilyPond markup (recursively).""" meth = self._markup.method(t) if meth: return meth(self, t) @_markup(lilypond.MarkupScore) def handle_markup_score(self, t): item = self.factory(MarkupScore, t) for t in self.consume(): if isinstance(t, lilypond.OpenBracket): item.tokens = (t,) def last(t): item.tokens += (t,) item.extend(self.read(self.consume(last))) return item elif not isinstance(t, lex.Space): self.source.pushback() break return item @_markup(lilypond.MarkupCommand) def handle_markup_command(self, t): item = self.factory(MarkupCommand, t) self.add_markup_arguments(item) return item @_markup(lilypond.MarkupUserCommand) def handle_markup_user_command(self, t): item = self.factory(MarkupUserCommand, t) return item @_markup(lilypond.OpenBracketMarkup) def handle_markup_open_bracket(self, t): item = self.factory(MarkupList, t) self.add_markup_arguments(item) return item @_markup(lilypond.MarkupWord) def handle_markup_word(self, t): return self.factory(MarkupWord, t) def add_markup_arguments(self, item): """Add markup arguments to the item.""" for t in self.consume(): i = self.read_markup(t) if i: item.append(i) elif isinstance(item, MarkupList) and isinstance(t, lilypond.CloseBracketMarkup): item.tokens = (t,) return item def read_scheme_item(self, t): """Reads a Scheme expression (just after the # in LilyPond mode).""" item = self.factory(Scheme, t) for t in self.consume(): if not isinstance(t, lex.Space): i = self.read_scheme(t) if i: item.append(i) break return item def read_scheme(self, t): """Return a Scheme item from the token t.""" meth = self._scheme.method(t) if meth: return meth(self, t) @_scheme(scheme.Quote) def handle_scheme_quote(self, t): item = self.factory(SchemeQuote, t) for t in self.consume(): if not isinstance(t, lex.Space): i = self.read_scheme(t) if i: item.append(i) break return item @_scheme(scheme.OpenParen) def handle_scheme_open_parenthesis(self, t): item = self.factory(SchemeList, t) def last(t): item.tokens = (t,) for t in self.consume(last): if not isinstance(t, lex.Space): i = self.read_scheme(t) if i: item.append(i) return item @_scheme( scheme.Dot, scheme.Bool, scheme.Char, scheme.Word, scheme.Number, scheme.Fraction, scheme.Float, ) def handle_scheme_token(self, t): return self.factory(SchemeItem, t) @_scheme(scheme.LilyPondStart) def handle_scheme_lilypond_start(self, t): item = self.factory(SchemeLily, t) def last(t): item.tokens = (t,) item.extend(self.read(self.consume(last))) return item python-ly-0.9.3/ly/music/event.py0000644000175000017500000000270112461001227020062 0ustar wilbertwilbert00000000000000# 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. """ Translates a music.items.Document tree into lists of events. """ from __future__ import unicode_literals class Events(object): """Traverses a music tree and records music events from it.""" unfold_repeats = False def read(self, node, time=0, scaling=1): """Read events from the node and all its child nodes; return time.""" return self.traverse(node, time, scaling) def traverse(self, node, time, scaling): """Traverse node and call event handlers; record and return the time.""" return node.events(self, time, scaling) python-ly-0.9.3/ly/music/items.py0000644000175000017500000011320612477577645020122 0ustar wilbertwilbert00000000000000# 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 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) 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 "" 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()) def events(self, e, time, scaling): """Let the event.Events instance handle the events. Return the time.""" return time def length(self): """Return the musical duration.""" return 0 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 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()) 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 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) 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 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()) 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) 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 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 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 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 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 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 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 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)) class Token(Item): """Any token that is not otherwise recognized""" class Container(Item): """An item having a list of child items.""" class Duration(Item): """A written duration""" class Durable(Item): """An Item that has a musical duration, in the duration attribute.""" duration = 0, 1 # two Fractions: (base, scaling) def length(self): """Return the musical duration (our base * our scaling).""" base, scaling = self.duration return base * scaling 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 class Chord(Durable, Container): pass class Unpitched(Durable): """A "note" without pitch, just a standalone duration.""" pitch = None class Note(Durable): """A Note that has a ly.pitch.Pitch""" pitch = None octave_token = None accidental_token = None octavecheck_token = None class Skip(Durable): pass class Rest(Durable): pass class Q(Durable): pass class DrumNote(Durable): pass class Music(Container): """Any music expression, to be inherited of.""" 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 def length(self): """Return the musical duration.""" from . import event return event.Events().read(self) 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 class MusicList(Music): """A music expression, either << >> or { }.""" simultaneous = False 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 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) class Tag(Music): r"""A \tag, \keepWithTag or \removeWithTag command.""" 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 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 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 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) 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 class Grace(Music): """Music that has grace timing, i.e. 0 as far as computation is concerned.""" 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) 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 class AfterGrace(Music): r"""The \afterGrace function with its two arguments. Only the duration of the first is counted. """ class PartCombine(Music): r"""The \partcombine command with 2 music arguments.""" 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 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 class Relative(Music): r"""A \relative music expression. Has one or two children (Note, Music).""" pass class Absolute(Music): r"""An \absolute music expression. Has one child (normally Music).""" pass class Transpose(Music): r"""A \transpose music expression. Has normally three children (Note, Note, Music).""" class Repeat(Music): r"""A \repeat expression.""" 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 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 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): 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 class Alternative(Music): r"""An \alternative expression.""" class InputMode(Music): """Base class for inputmode-changing commands.""" class NoteMode(InputMode): r"""A \notemode or \notes expression.""" class ChordMode(InputMode): r"""A \chordmode or \chords expression.""" class DrumMode(InputMode): r"""A \drummode or \drums expression.""" class FigureMode(InputMode): r"""A \figuremode or \figures expression.""" class LyricMode(InputMode): r"""A \lyricmode, \lyrics or \addlyrics expression.""" class LyricsTo(InputMode): r"""A \lyricsto expression.""" _context_id = None 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 class LyricText(Durable): """A lyric text (word, markup or string), with a Duration.""" class LyricItem(Item): """Another lyric item (skip, extender, hyphen or tie).""" class ChordSpecifier(Item): """Chord specifications after a note in chord mode. Has children of Note or ChordItem class. """ class ChordItem(Item): """An item inside a ChordSpecifier, e.g. a number or modifier.""" class Tremolo(Item): """A tremolo item ":". The duration attribute is a tuple (base, scaling).""" duration = 0, 1 class Translator(Item): r"""Base class for a \change, \new, or \context music expression.""" _context = None _context_id = None def context(self): return self._context 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 class Context(Translator, Music): r"""A \new or \context music expression.""" class Change(Translator): r"""A \change music expression.""" class Tempo(Item): duration = 0, 1 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 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 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 class TimeSignature(Item): r"""A \time command.""" _num = 4 _fraction = Fraction(1, 4) _beatstructure = None def measure_length(self): """The length of one measure in this time signature as a Fraction.""" return self._num * self._fraction def numerator(self): """The upper number (e.g. for 3/2 it returns 3).""" return self._num def fraction(self): """The lower number as a Fraction (e.g. for 3/2 it returns 1/2).""" return self._fraction def beatstructure(self): """The scheme expressions denoting the beat structure, if specified.""" return self._beatstructure class Partial(Item): r"""A \partial command.""" duration = 0, 1 def partial_length(self): """Return the duration given as argument as a Fraction.""" base, scaling = self.duration return base * scaling class Clef(Item): r"""A \clef item.""" _specifier = None def specifier(self): if isinstance(self._specifier, String): return self._specifier.value() return self._specifier class KeySignature(Item): r"""A \key pitch \mode command.""" def pitch(self): """The ly.pitch.Pitch that denotes the pitch.""" for i in self.find(Note): return i.pitch def mode(self): """The mode, e.g. "major", "minor", etc.""" for i in self.find(Command): return i.token[1:] class PipeSymbol(Item): r"""A pipe symbol: |""" class VoiceSeparator(Item): r"""A voice separator: \\""" class Postfix(Item): """Any item that is prefixed with a _, - or ^ direction token.""" class Tie(Item): """A tie.""" class Slur(Item): """A ( or ).""" event = None class PhrasingSlur(Item): r"""A \( or \).""" event = None class Beam(Item): """A [ or ].""" event = None class Dynamic(Item): """Any dynamic symbol.""" class Articulation(Item): """An articulation, fingering, string number, or other symbol.""" class StringTuning(Item): r"""A \stringTuning command (with a chord as argument).""" class Keyword(Item): """A LilyPond keyword.""" class Command(Item): """A LilyPond command.""" class UserCommand(Music): """A user command, most probably referring to music.""" def name(self): """Return the name of this user command (without the leading backslash).""" return self.token[1:] 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() 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 class Version(Item): r"""A \version command.""" 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 '' def version(self): """The version as a tuple of ints.""" return tuple(map(int, re.findall(r'\d+', self.version_string()))) class Include(Item): r"""An \include command (not changing the language).""" 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() class Language(Item): r"""A command (\language or certain \include commands) that changes the pitch language.""" language = None class Markup(Item): r"""A command starting markup (\markup, -lines and -list).""" def plaintext(self): """Return the plain text value of this node.""" return ' '.join(n.plaintext() for n in self) class MarkupCommand(Item): r"""A markup command, such as \italic etc.""" 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) class MarkupUserCommand(Item): """A user-defined markup command""" def name(self): """Return the name of this user command (without the leading backslash).""" return self.token[1:] 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 class MarkupScore(Item): r"""A \score inside Markup.""" class MarkupList(Item): r"""The group of markup items inside { and }. NOTE: *not* a \markuplist.""" def plaintext(self): """Return the plain text value of this node.""" return ' '.join(n.plaintext() for n in self) class MarkupWord(Item): """A MarkupWord token.""" def plaintext(self): return self.token class Assignment(Item): """A variable = value construct.""" def name(self): """The variable name.""" return self.token def value(self): """The assigned value.""" if len(self): return self[-1] class Book(Container): r"""A \book { ... } construct.""" class BookPart(Container): r"""A \bookpart { ... } construct.""" class Score(Container): r"""A \score { ... } construct.""" class Header(Container): r"""A \header { ... } construct.""" class Paper(Container): r"""A \paper { ... } construct.""" class Layout(Container): r"""A \layout { ... } construct.""" class Midi(Container): r"""A \midi { ... } construct.""" class LayoutContext(Container): r"""A \context { ... } construct within Layout or Midi.""" class With(Container): r"""A \with ... construct.""" class Set(Item): r"""A \set command.""" def context(self): """The context, if specified.""" for t in self.tokens: if isinstance(t, lilypond.ContextName): return t 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 def value(self): """The value, given as argument. This is simply the child element.""" for i in self: return i class Unset(Item): """An \\unset command.""" def context(self): """The context, if specified.""" for t in self.tokens: if isinstance(t, lilypond.ContextName): return t 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 class Override(Item): r"""An \override command.""" def context(self): for i in self: if isinstance(i.token, lilypond.ContextName): return i.token def grob(self): for i in self: if isinstance(i.token, lilypond.GrobName): return i.token class Revert(Item): r"""A \revert command.""" def context(self): for i in self: if isinstance(i.token, lilypond.ContextName): return i.token def grob(self): for i in self: if isinstance(i.token, lilypond.GrobName): return i.token class Tweak(Item): r"""A \tweak command.""" class PathItem(Item): r"""An item in the path of an \override or \revert command.""" class String(Item): """A double-quoted string.""" 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() def value(self): return ''.join( t[1:] if isinstance(t, lex.Character) and t.startswith('\\') else t for t in self.tokens[:-1]) class Number(Item): """A numerical value, directly entered.""" 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) class Scheme(Item): """A Scheme expression inside LilyPond.""" def plaintext(self): """A crude way to get the plain text in this node.""" # TEMP use get_string() return self.get_string() 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]) 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()] 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) 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) 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)) 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])) class SchemeItem(Item): """Any scheme token.""" class SchemeList(Container): """A ( ... ) expression.""" class SchemeQuote(Item): """A ' in scheme.""" class SchemeLily(Container): """A music expression inside #{ and #}.""" python-ly-0.9.3/ly/dom.py0000644000175000017500000006201012503766206016413 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ LilyPond DOM (c) 2008-2011 Wilbert Berendsen License: GPL. A simple Document Object Model for LilyPond documents. The purpose is to easily build a LilyPond document with good syntax, not to fully understand all features LilyPond supports. (This DOM does not enforce a legal LilyPond file.) All elements of a LilyPond document inherit Node. Note: elements keep a weak reference to their parent. """ from __future__ import unicode_literals from __future__ import absolute_import # prevent picking old stale node.py from package try: string_types = basestring except NameError: string_types = str import fractions import re import ly.pitch import ly.duration from ly.node import WeakNode class LyNode(WeakNode): """ Base class for LilyPond objects, based on Node, which takes care of the tree structure. """ ## # True if this element is single LilyPond atom, word, note, etc. # When it is the only element inside { }, the brackets can be removed. isAtom = False ## # The number of newlines this object wants before it. before = 0 ## # The number of newlines this object wants after it. after = 0 def ly(self, printer): """ Returns printable output for this object. Can ask printer for certain settings, e.g. pitch language etc. """ return '' def concat(self, other): """ Returns a string with newlines to concat this node to another one. If zero newlines are requested, an empty string is returned. """ return '\n' * max(self.after, other.before) ## # Leaf and Container are the two base classes the rest of the LilyPond # element classes is based on. # class Leaf(LyNode): """ A leaf node without children """ pass class Container(LyNode): """ A node that concatenates its children on output """ ## # default character to concatenate children with defaultSpace = " " @property def before(self): if len(self): return self[0].before else: return 0 @property def after(self): if len(self): return self[-1].after else: return 0 def ly(self, printer): if len(self) == 0: return '' else: n = self[0] res = [n.ly(printer)] for m in self[1:]: res.append(n.concat(m) or self.defaultSpace) res.append(m.ly(printer)) n = m return "".join(res) ## # Helper classes # class Printer(object): """ Performs certain operations on behalf of a LyNode tree, like quoting strings or translating pitch names, etc. """ # You may change these primary_quote_left = '\u2018' primary_quote_right = '\u2019' secondary_quote_left = '\u201C' secondary_quote_right = '\u201D' def __init__(self): self.typographicalQuotes = True self.language = "nederlands" self.indentString = ' ' def quoteString(self, text): if self.typographicalQuotes: text = re.sub(r'"(.*?)"', self.primary_quote_left + r'\1' + self.primary_quote_right, text) text = re.sub(r"'(.*?)'", self.secondary_quote_left + r'\1' + self.secondary_quote_right, text) text = text.replace("'", '\u2018') # escape regular double quotes text = text.replace('"', '\\"') # quote the string return '"{0}"'.format(text) def indentGen(self, node, startIndent = 0): """ A generator that walks on the output of the given node, and returns properly indented LilyPond code. """ d = startIndent for t in node.ly(self).splitlines() + [''] * node.after: if d and re.match(r'#?}|>|%}', t): d -= 1 yield self.indentString * d + t if re.search(r'(\{|<|%{)$', t): d += 1 def indent(self, node): """ Return a formatted printout of node (and its children) """ return '\n'.join(self.indentGen(node)) class Reference(object): """ A simple object that keeps a name, to use as a (context) identifier. Set the name attribute to the name you want to display, and on all places in the document the name will show up. """ def __init__(self, name=""): self.name = name def __format__(self, format_spec): return self.name class Named(object): r""" Mixin to print a \name before the contents of the container. format() is called on the self.name attribute, so it may also be a Reference. """ name = "" def ly(self, printer): return "\\{0} {1}".format(self.name, super(Named, self).ly(printer)) class HandleVars(object): """ A powerful mixin class to facilitate handling unique variable assignments inside a Container more. E.g.: >>> h = Header() >>> h['composer'] = "Johann Sebastian Bach" creates a subnode (by default Assignment) with the name 'composer', and that node again gets an autogenerated subnode of type QuotedString (if the argument wasn't already a Node). """ childClass = None # To be filled in later def ifbasestring(func): """ Ensure that the method is only called for basestring objects. Otherwise the same method from the super class is called. """ def newfunc(obj, name, *args): if isinstance(name, string_types): return func(obj, name, *args) else: f = getattr(super(HandleVars, obj), func.__name__) return f(name, *args) return newfunc @ifbasestring def __getitem__(self, name): for node in self.find_children(self.childClass, 1): if node.name == name: return node @ifbasestring def __setitem__(self, name, valueObj): if not isinstance(valueObj, LyNode): valueObj = self.importNode(valueObj) assignment = self[name] if assignment: assignment.setValue(valueObj) else: self.childClass(name, self, valueObj) @ifbasestring def __contains__(self, name): return bool(self[name]) @ifbasestring def __delitem__(self, name): h = self[name] if h: self.remove(h) def importNode(self, obj): """ Try to interpret the object and transform it into a Node object of the right species. """ return QuotedString(obj) class AddDuration(object): """ Mixin to add a duration (as child). """ def ly(self, printer): s = super(AddDuration, self).ly(printer) dur = self.find_child(Duration, 1) if dur: s += dur.ly(printer) return s class Block(Container): """ A vertical container type that puts everything on a new line. """ defaultSpace = "\n" before, after = 1, 1 class Document(Container): """ A container type that puts everything on a new line. To be used as a full LilyPond document. """ defaultSpace = "\n" after = 1 ## # These classes correspond to real LilyPond data. # class Text(Leaf): """ A leaf node with arbitrary text """ def __init__(self, text="", parent=None): super(Text, self).__init__(parent) self.text = text def ly(self, printer): return self.text class TextDur(AddDuration, Text): """ A text note with an optional duration as child. """ pass class Line(Text): """ A text node that claims its own line. """ before, after = 1, 1 class Comment(Text): """ A LilyPond comment at the end of a line """ after = 1 def ly(self, printer): return re.compile('^', re.M).sub('% ', self.text) class LineComment(Comment): """ A LilyPond comment that takes a full line """ before = 1 class BlockComment(Comment): """ A block comment between %{ and %} """ @property def before(self): return '\n' in self.text and 1 or 0 @property def after(self): return '\n' in self.text and 1 or 0 def ly(self, printer): text = self.text.replace('%}', '') f = "%{{\n{0}\n%}}" if '\n' in text else "%{{ {0} %}}" return f.format(text) class QuotedString(Text): """ A string that is output inside double quotes. """ isAtom = True def ly(self, printer): # we call format(), since self.text MIGHT be a Reference... return printer.quoteString(format(self.text)) class Newline(LyNode): """ A newline. """ after = 1 class BlankLine(Newline): """ A blank line. """ before = 1 class Scheme(Text): """ A Scheme expression, without the extra # prepended """ isAtom = True def ly(self, printer): return '#' + self.text class Version(Line): """ a LilyPond version instruction """ def ly(self, printer): return r'\version "{0}"'.format(self.text) class Include(Line): r""" a LilyPond \include statement """ def ly(self, printer): return r'\include "{0}"'.format(self.text) class Assignment(Container): """ A varname = value construct with it's value as its first child The name can be a string or a Reference object: so that everywhere where this varname is referenced, the name is the same. """ before, after = 1, 1 def __init__(self, name=None, parent=None, valueObj=None): super(Assignment, self).__init__(parent) self.name = name if valueObj: self.append(valueObj) # Convenience methods: def setValue(self, obj): if len(self): self[0] = obj else: self.append(obj) def value(self): if len(self): return self[0] def ly(self, printer): return "{0} = {1}".format(self.name, super(Assignment, self).ly(printer)) HandleVars.childClass = Assignment class Identifier(Leaf): """ An identifier, prints as \\name. Name may be a string or a Reference object. """ isAtom = True def __init__(self, name=None, parent=None): super(Identifier, self).__init__(parent) self.name = name def ly(self, printer): return "\\{0}".format(self.name) class Statement(Named, Container): """ Base class for statements with arguments. The statement is read in the name attribute, the arguments are the children. """ before = 0 # do not read property from container isAtom = True class Command(Statement): """ Use this to create a LilyPond command supplying the name (or a Reference) when instantiating. """ def __init__(self, name, parent=None): super(Command, self).__init__(parent) self.name = name class Enclosed(Container): """ Encloses all children between brackets: { ... } If may_remove_brackets is True in subclasses, the brackets are removed if there is only one child and that child is an atom (i.e. a single LilyPond expression. """ may_remove_brackets = False pre, post = "{", "}" before, after = 0, 0 isAtom = True def ly(self, printer): if len(self) == 0: return " ".join((self.pre, self.post)) sup = super(Enclosed, self) text = sup.ly(printer) if self.may_remove_brackets and len(self) == 1 and self[0].isAtom: return text elif sup.before or sup.after or '\n' in text: return "".join((self.pre, "\n" * max(sup.before, 1), text, "\n" * max(sup.after, 1), self.post)) else: return " ".join((self.pre, text, self.post)) class Seq(Enclosed): """ An SequentialMusic expression between { } """ pre, post = "{", "}" class Sim(Enclosed): """ An SimultaneousMusic expression between << >> """ pre, post = "<<", ">>" class Seqr(Seq): may_remove_brackets = True class Simr(Sim): may_remove_brackets = True class SchemeLily(Enclosed): """ A LilyPond expression between #{ #} (inside scheme) """ pre, post = "#{", "#}" class SchemeList(Enclosed): """ A list of items enclosed in parentheses """ pre, post = "(", ")" def ly(self, printer): return self.pre + Container.ly(self, printer) + self.post class StatementEnclosed(Named, Enclosed): """ Base class for LilyPond commands that have a single bracket-enclosed list of arguments. """ may_remove_brackets = True class CommandEnclosed(StatementEnclosed): """ Use this to print LilyPond commands that have a single bracket-enclosed list of arguments. The command name is supplied to the constructor. """ def __init__(self, name, parent=None): super(CommandEnclosed, self).__init__(parent) self.name = name class Section(StatementEnclosed): """ Very much like a Statement. Use as base class for \\book { }, \\score { } etc. By default never removes the brackets and always starts on a new line. """ may_remove_brackets = False before, after = 1, 1 class Book(Section): name = 'book' class BookPart(Section): name = 'bookpart' class Score(Section): name = 'score' class Paper(HandleVars, Section): name = 'paper' class Layout(HandleVars, Section): name = 'layout' class Midi(HandleVars, Section): name = 'midi' class Header(HandleVars, Section): name = 'header' class With(HandleVars, Section): """ If this item has no children, it prints nothing. """ name = 'with' before, after = 0, 0 def ly(self, printer): if len(self): return super(With, self).ly(printer) else: return '' class ContextName(Text): """ Used to print a context name, like \\Score. """ def ly(self, printer): return "\\" + self.text class Context(HandleVars, Section): r""" A \context section for use inside Layout or Midi sections. """ name = 'context' def __init__(self, contextName="", parent=None): super(Context, self).__init__(parent) if contextName: ContextName(contextName, self) class ContextType(Container): r""" \new or \context Staff = 'bla' \with { } << music >> A \with (With) element is added automatically as the first child as soon as you use our convenience methods that manipulate the variables in \with. If the \with element is empty, it does not print anything. You should add one other music object to this. """ before, after = 1, 1 isAtom = True ctype = None def __init__(self, cid=None, new=True, parent=None): super(ContextType, self).__init__(parent) self.new = new self.cid = cid def ly(self, printer): res = [] res.append(self.new and "\\new" or "\\context") res.append(self.ctype or self.__class__.__name__) if self.cid: res.append("=") res.append(printer.quoteString(format(self.cid))) res.append(super(ContextType, self).ly(printer)) return " ".join(res) def getWith(self): """ Gets the attached with clause. Creates it if not there. """ for node in self: if isinstance(node, With): return node self.insert(0, With()) return self[0] def addInstrumentNameEngraverIfNecessary(self): """ Adds the Instrument_name_engraver to the node if it would need it to print instrument names. """ if not isinstance(self, (Staff, RhythmicStaff, PianoStaff, Lyrics, FretBoards)): Line('\\consists "Instrument_name_engraver"', self.getWith()) class ChoirStaff(ContextType): pass class ChordNames(ContextType): pass class CueVoice(ContextType): pass class Devnull(ContextType): pass class DrumStaff(ContextType): pass class DrumVoice(ContextType): pass class Dynamics(ContextType): pass class FiguredBass(ContextType): pass class FretBoards(ContextType): pass class Global(ContextType): pass class GrandStaff(ContextType): pass class GregorianTranscriptionStaff(ContextType): pass class GregorianTranscriptionVoice(ContextType): pass class InnerChoirStaff(ContextType): pass class InnerStaffGroup(ContextType): pass class Lyrics(ContextType): pass class MensuralStaff(ContextType): pass class MensuralVoice(ContextType): pass class NoteNames(ContextType): pass class PianoStaff(ContextType): pass class RhythmicStaff(ContextType): pass class ScoreContext(ContextType): r""" Represents the Score context in LilyPond, but the name would collide with the Score class that represents \score { } constructs. Because the latter is used more often, use ScoreContext to get \new Score etc. """ ctype = 'Score' class Staff(ContextType): pass class StaffGroup(ContextType): pass class TabStaff(ContextType): pass class TabVoice(ContextType): pass class VaticanaStaff(ContextType): pass class VaticanaVoice(ContextType): pass class Voice(ContextType): pass class UserContext(ContextType): r""" Represents a context the user creates. e.g. \new MyStaff = cid << music >> """ def __init__(self, ctype, cid=None, new=True, parent=None): super(UserContext, self).__init__(cid, new, parent) self.ctype = ctype class ContextProperty(Leaf): """ A Context.property or Context.layoutObject construct. Call e.g. ContextProperty('aDueText', 'Staff') to get 'Staff.aDueText'. """ def __init__(self, prop, context=None, parent=None): self.prop = prop self.context = context def ly(self, printer): if self.context: # In \lyrics or \lyricmode: put spaces around dot. p = self.find_parent(InputMode) if p and isinstance(p, LyricMode): f = '{0} . {1}' else: f = '{0}.{1}' return f.format(self.context, self.prop) else: return self.prop class InputMode(StatementEnclosed): """ The abstract base class for input modes such as lyricmode/lyrics, chordmode/chords etc. """ pass class ChordMode(InputMode): name = 'chordmode' class InputChords(ChordMode): name = 'chords' class LyricMode(InputMode): name = 'lyricmode' class InputLyrics(LyricMode): name = 'lyrics' class NoteMode(InputMode): name = 'notemode' class InputNotes(NoteMode): name = 'notes' class FigureMode(InputMode): name = 'figuremode' class InputFigures(FigureMode): name = 'figures' class DrumMode(InputMode): name = 'drummode' class InputDrums(DrumMode): name = 'drums' class AddLyrics(InputLyrics): name = 'addlyrics' may_remove_brackets = False before, after = 1, 1 class LyricsTo(LyricMode): name = 'lyricsto' def __init__(self, cid, parent=None): super(LyricsTo, self).__init__(parent) self.cid = cid def ly(self, printer): res = ["\\" + self.name] res.append(printer.quoteString(format(self.cid))) res.append(Enclosed.ly(self, printer)) return " ".join(res) class Pitch(Leaf): """ A pitch with octave, note, alter. octave is specified by an integer, zero for the octave containing middle C. note is a number from 0 to 6, with 0 corresponding to pitch C and 6 corresponding to pitch B. alter is the number of whole tones for alteration (can be int or Fraction) """ def __init__(self, octave=0, note=0, alter=0, parent=None): super(Pitch, self).__init__(parent) self.octave = octave self.note = note self.alter = fractions.Fraction(alter) def ly(self, printer): """ Print the pitch in the preferred language. """ p = ly.pitch.pitchWriter(printer.language)(self.note, self.alter) if self.octave < -1: return p + ',' * (-self.octave - 1) elif self.octave > -1: return p + "'" * (self.octave + 1) return p class Duration(Leaf): r""" A duration with duration (in logarithmic form): (-2 ... 8), where -2 = \longa, -1 = \breve, 0 = 1, 1 = 2, 2 = 4, 3 = 8, 4 = 16, etc, dots (number of dots), factor (Fraction giving the scaling of the duration). """ def __init__(self, dur, dots=0, factor=1, parent=None): super(Duration, self).__init__(parent) self.dur = dur # log self.dots = dots self.factor = fractions.Fraction(factor) def ly(self, printer): return ly.duration.tostring(self.dur, self.dots, self.factor) class Chord(Container): """ A chord containing one of more Pitches and optionally one Duration. This is a bit of a hack, awaiting real music object support. """ def ly(self, printer): pitches = list(self.find_children(Pitch, 1)) if len(pitches) == 1: s = pitches[0].ly(printer) else: s = "<{0}>".format(' '.join(p.ly(printer) for p in pitches)) duration = self.find_child(Duration, 1) if duration: s += duration.ly(printer) return s class Relative(Statement): r""" \relative music You should add a Pitch (optionally) and another music object, e.g. Sim or Seq, etc. """ name = 'relative' class Transposition(Statement): r""" \transposition You should add a Pitch. """ name = 'transposition' class KeySignature(Leaf): r""" A key signature expression, like: \key c \major The pitch should be given in the arguments note and alter and is written out in the document's language. """ def __init__(self, note=0, alter=0, mode="major", parent=None): super(KeySignature, self).__init__(parent) self.note = note self.alter = fractions.Fraction(alter) self.mode = mode def ly(self, printer): pitch = ly.pitch.pitchWriter(printer.language)(self.note, self.alter) return "\\key {0} \\{1}".format(pitch, self.mode) class TimeSignature(Leaf): r""" A time signature, like: \time 4/4 """ def __init__(self, num, beat, parent=None): super(TimeSignature, self).__init__(parent) self.num = num self.beat = beat def ly(self, printer): return "\\time {0}/{1}".format(self.num, self.beat) class Partial(Named, Duration): r""" \partial You should add a Duration element """ name = "partial" before, after = 1, 1 class Tempo(Container): r""" A tempo setting, like: \tempo 4 = 100 May have a child markup or quoted string. """ before, after = 1, 1 def __init__(self, duration, value, parent=None): super(Tempo, self).__init__(parent) self.duration = duration self.value = value def ly(self, printer): result = ['\\tempo'] if len(self) > 0: result.append(super(Tempo, self).ly(printer)) if self.value: result.append("{0}={1}".format(self.duration, self.value)) return ' '.join(result) class Clef(Leaf): """ A clef. """ def __init__(self, clef, parent=None): super(Clef, self).__init__(parent) self.clef = clef def ly(self, printer): clef = self.clef if self.clef.isalpha() else '"{0}"'.format(self.clef) return "\\clef " + clef class VoiceSeparator(Leaf): r""" A Voice Separator: \\ """ def ly(self, printer): return r'\\' class Mark(Statement): r""" The \mark command. """ name = 'mark' class Markup(StatementEnclosed): r""" The \markup command. You can add many children, in that case Markup automatically prints { and } around them. """ name = 'markup' class MarkupEnclosed(CommandEnclosed): """ A markup that auto-encloses all its arguments, like 'italic', 'bold' etc. You must supply the name. """ pass class MarkupCommand(Command): """ A markup command with more or no arguments, that does not auto-enclose its arguments. Useful for commands like note-by-number or hspace. You must supply the name. Its arguments are its children. If one argument can be a markup list, use a Enclosed() construct for that. """ pass python-ly-0.9.3/ly/xml/0000775000175000017500000000000012636745216016072 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/xml/xml-export-init.ly0000644000175000017500000000402012466604434021507 0ustar wilbertwilbert00000000000000%{ This script is intended to be used via the --init option. It automatically converts every \book in the score to an XML document (LilyPond always creates at least one book). In this case the XML is also written to standard output, but you can specify another file with -dxml-export=. So, to convert a LilyPond source file to an XML file containing the LilyPond music structure in XML format, use the following command: lilypond --init /path/to/xml-export-init.ly -dxml-export=song.xml song.ly The XML document has a root element, containing a element for every book in the LilyPond file. %} %% find our companion script, but leave the old setting of relative-includes #(define old-option-relative-includes (ly:get-option 'relative-includes)) #(ly:set-option 'relative-includes #t) \include "xml-export.ily" #(ly:set-option 'relative-includes old-option-relative-includes) %% make a toplevel book handler #(define (make-toplevel-book-handler->xml xml) "Return a book handler that dumps a book to specified XML instance" (lambda (parser book) (obj->lily-xml book xml))) %% create the XML output instance (in the toplevel scope) #(begin (define xml-outputter #f) (let* ((x (ly:get-option 'xml-export)) (xml-file (if x (symbol->string x) "-")) (port (if (string=? xml-file "-") (current-output-port) (open-output-file xml-file))) (xml (XML port))) (ly:message (format "Writing XML to ~a..." (if (string=? xml-file "-") "standard output" xml-file))) (set! xml-outputter xml))) %% create the output document and run the normal init procedure #(xml-outputter 'declaration) #(xml-outputter 'open-tag 'document) %% HACK, overwrite print-book-with-defaults, because toplevel-book-handler %% is overwritten by the declarations-init.ly script. #(define print-book-with-defaults (make-toplevel-book-handler->xml xml-outputter)) \include "init.ly" #(xml-outputter 'close-tag) #(ly:message "Writing XML completed.") python-ly-0.9.3/ly/xml/__init__.py0000644000175000017500000000341112476157645020205 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. r""" Routines that manipulate an XML mapping very similar to the Scheme music structure used by LilyPond itself. The mapping can also be generated from within LilyPond and then parsed by other programs. While designing the mapping, I decided to use xml elements for almost everything, only values that are very simple in all cases, are attributes. Code could be written to convert such an XML music tree to other formats. Also code is added to build such trees from scratch and from tokenized documents. Code will be added to print LilyPond source. When all is finished, `ly.dom` is deprecated and `ly.music` will probably use these xml tree for storage. A single LilyPond file `xml-export.ily` is also included with this module; it can be \included in a LilyPond document and exports the ``\displayLilyXML`` music function. """ from __future__ import unicode_literals from __future__ import absolute_import python-ly-0.9.3/ly/xml/xml-export.ily0000644000175000017500000003734212467567477020754 0ustar wilbertwilbert00000000000000\version "2.18.2" %{ xml-export.ily Written by Wilbert Berendsen, jan-feb 2015 This LilyPond module defines a function (xml-export) that converts LilyPond datastructures to XML. For convenience, a \displayLilyXML music function is added that converts a music expression to XML. Usage e.g.: \include "/path/to/xml-export.ily" \displayLilyXML { c d e f } The XML closely follows the LilyPond music structure. All (make-music 'MusicName ...) objects translate to a tag. The music in the 'element and 'elements properties is put in the and tags. (LilyPond uses 'element when there is a single music argument, and 'elements for a list of music arguments, but for example \repeat uses both: 'element for the repeated music and 'elements for the \alternatives.) Thus , if there, always has one child. , if there, can have more than one child. Besides 'element and 'elements, the following properties of music objects are handled specially: - 'origin => element with filename, line and char attributes - 'pitch => element with octave, notename and alteration attributes - 'duration => element with log, dots, numer and denom attributes - 'articulations => element containing elements - 'tweaks => element containing pairs (symbol . value) All other properties a music object may have, are translated to a element with a name attribute. The value is the child element and can be any object (string, list, pair, symbol, number etc.). (Note that the LilyPond command \displayMusic does not display all properties.) Markup objects are also converted to XML, where a toplevel element is used. The individual markup commands are converted to an element, with the name in the name attribute (e.g. ). Arguments to markup commands may be other commands, or other objects (markup \score even has a score argument, which is also supported). Example: This LilyPond music: \relative { c d e } maps to Scheme (using \displayMusic): (make-music 'RelativeOctaveMusic 'element (make-music 'SequentialMusic 'elements (list (make-music 'NoteEvent 'pitch (ly:make-pitch -1 0 0) 'duration (ly:make-duration 2 0 1)) (make-music 'NoteEvent 'pitch (ly:make-pitch -1 1 0) 'duration (ly:make-duration 2 0 1)) (make-music 'NoteEvent 'pitch (ly:make-pitch -1 2 0) 'duration (ly:make-duration 2 0 1))))) and maps to XML (using \displayLilyXML): To automatically export a full LilyPond document to an XML representation, use the xml-export-init.ly script with the --init LilyPond option. That script automatically sets up LilyPond to output one XML document with a root element, containing a element for every book in the LilyPond file. (LilyPond always creates at least one book, collecting all the music or markup at the toplevel.) %} % convert an assoc list to an xml attribute string (joined with a space in between) #(define (attrs->string attrs) (string-join (map (lambda (e) (attr->string (car e) (cdr e))) attrs) " " 'prefix)) % convert a name value pair to an xml attribute % name is a symbol, value can be a symbol, string, or number #(define (attr->string name value) (string-append (symbol->string name) "=\"" (cond ((string? value) (attribute-escape value)) ((number? value) (number->string value)) ((symbol? value) (attribute-escape (symbol->string value)))) "\"")) % escape string for xml body #(define (xml-escape s) (ly:string-substitute "<" "<" (ly:string-substitute ">" ">" (ly:string-substitute "\"" """ (ly:string-substitute "&" "&" s))))) % escape string for xml attribute #(define (attribute-escape s) (ly:string-substitute "\n" " " (ly:string-substitute "\"" """ (ly:string-substitute "&" "&" s)))) % a nice class that outputs an XML document % (define x (XML port) ;; port is optional % (x 'open-tag 'name attrs) % (x 'open-close-tag 'name attrs) % (x 'close-tag) % when an open tag is closed and it has no child tags, it is automatically % written to output as an open-close tag. #(define XML (lambda args (define indent-width 2) (define pending #f) (define tags '()) (define port (if (pair? args) (car args) (current-output-port))) (define (output-xml-tag indent tag-name attrs text how) "output an XML tag. indent: number of spaces before it tag-name: symbol attrs: assoc list text: text between open and close tag (how must be 'text-tag) how can be: 'open-tag: write an open-tag with attributes 'close-tag: write a close-tag (attrs are ignored) 'open-close-tag: write a self-closing tag 'text-tag: write a open and close tag with text text " (let ((s (string-append (make-string (* indent indent-width) #\space) "<" (if (eq? how 'close-tag) "/" "") (symbol->string tag-name) (if (eq? how 'close-tag) "" (attrs->string attrs)) (if (eq? how 'open-close-tag) "/" "") ">" (if (eq? how 'text-tag) (string-append (xml-escape text) "string tag-name) ">") "") "\n"))) (display s port))) (define (output-last-tag how) "output the last tag on the tags stack." (let ((indent (1- (length tags))) (args (car tags))) (apply (lambda (tag-name attrs) (output-xml-tag indent tag-name attrs "" how)) args))) (define (declaration) "output an XML declaration." (display "\n" port)) (define (open-tag tag-name attrs) "implementation of open-tag method." (if pending (output-last-tag 'open-tag)) (set! tags (cons (list tag-name attrs) tags)) (set! pending #t)) (define (close-tag) "implementation of close-tag method." (if pending (output-last-tag 'open-close-tag) (output-last-tag 'close-tag)) (set! pending #f) (set! tags (cdr tags))) (define (text-tag tag-name text attrs) "implementation of text-tag method." (if pending (output-last-tag 'open-tag)) (output-xml-tag (length tags) tag-name attrs text 'text-tag) (set! pending #f)) (lambda (method-name . args) "call a method. 'declaration 'open-tag tag-name [attrs] 'close-tag 'open-close-tag tag-name [attrs] 'text-tag tag-name text [attrs] " (let* ((l (length args)) (tag-name (if (> l 0) (list-ref args 0))) (text (if (and (> l 1) (string? (list-ref args 1))) (list-ref args 1) "")) (attrs (if (and (> l 1) (list? (list-ref args (1- l)))) (list-ref args (1- l)) '()))) (case method-name ((declaration) (declaration)) ((open-tag) (open-tag tag-name attrs)) ((close-tag) (close-tag)) ((open-close-tag) (open-tag tag-name attrs) (close-tag)) ((text-tag) (text-tag tag-name text attrs))))))) % convert a markup object to XML #(define (markup->lily-xml mkup xml) (define (cmd-name proc) "return the name of the markup procedure" (symbol->string (procedure-name proc))) (define (mkuparg->xml arg) "convert markup arguments to xml" (cond ((markup-list? arg) ;; markup list (for-each mkup->xml arg)) ((markup? arg) ;; markup (mkup->xml arg)) (else ;; can be another scheme object (obj->lily-xml arg xml)))) (define (mkup->xml mkup) "convert a markup object to xml" (if (string? mkup) (xml 'text-tag 'string mkup) (begin (xml 'open-tag 'm `((name . ,(cmd-name (car mkup))))) (for-each mkuparg->xml (cdr mkup)) (xml 'close-tag)))) ;; wrap markup in a toplevel tag (xml 'open-tag 'markup) (mkuparg->xml mkup) (xml 'close-tag)) % convert a header to XML #(define (header->lily-xml header xml) (if (module? header) (let ((variables (filter (lambda (v) (not (eq? (car v) '%module-public-interface))) (ly:module->alist header)))) (if (pair? variables) (begin (xml 'open-tag 'header) (for-each (lambda (v) (xml 'open-tag 'variable `((name . ,(car v)))) (obj->lily-xml (cdr v) xml) (xml 'close-tag)) variables) (xml 'close-tag)))))) % convert any object to XML % currently the xml is just (display)ed but later it will be written to a file or string. % xml is an XML instance #(define (obj->lily-xml o xml) (cond ((ly:music? o) (let ((name (ly:music-property o 'name)) (e (ly:music-property o 'element)) (es (ly:music-property o 'elements)) (as (ly:music-property o 'articulations)) (tw (ly:music-property o 'tweaks)) (location (ly:music-property o 'origin)) (pitch (ly:music-property o 'pitch)) (duration (ly:music-property o 'duration)) (properties (filter (lambda (prop) (not (memq (car prop) '(name element elements articulations tweaks origin pitch duration)))) (ly:music-mutable-properties o))) ) (xml 'open-tag 'music `((name . ,name))) (if (ly:input-location? location) (obj->lily-xml location xml)) (if (ly:pitch? pitch) (obj->lily-xml pitch xml)) (if (ly:duration? duration) (obj->lily-xml duration xml)) (if (ly:music? e) (begin (xml 'open-tag 'element) (obj->lily-xml e xml) (xml 'close-tag))) (if (and (list? es) (not (null? es))) (begin (xml 'open-tag 'elements) (for-each (lambda (e) (obj->lily-xml e xml)) es) (xml 'close-tag 'elements))) (if (and (list? as) (not (null? as))) (begin (xml 'open-tag 'articulations) (for-each (lambda (e) (obj->lily-xml e xml)) as) (xml 'close-tag 'articulations ))) (if (and (list? tw) (not (null? tw))) (begin (xml 'open-tag 'tweaks) (for-each (lambda (e) (obj->lily-xml e xml)) tw) (xml 'close-tag 'tweaks))) (for-each (lambda (prop) (xml 'open-tag 'property `((name . ,(car prop)))) (obj->lily-xml (cdr prop) xml) (xml 'close-tag)) properties) (xml 'close-tag))) ((ly:moment? o) (xml 'open-close-tag 'moment `((main-numer . ,(ly:moment-main-numerator o)) (main-denum . ,(ly:moment-main-denominator o)) (grace-numer . ,(ly:moment-grace-numerator o)) (grace-denum . ,(ly:moment-grace-denominator o))))) ((ly:input-location? o) (let ((origin (ly:input-file-line-char-column o))) (xml 'open-close-tag 'origin `((filename . ,(car origin)) (line . ,(cadr origin)) (char . ,(caddr origin)))))) ((ly:pitch? o) (xml 'open-close-tag 'pitch `((octave . ,(ly:pitch-octave o)) (notename . ,(ly:pitch-notename o)) (alteration . ,(ly:pitch-alteration o))))) ((ly:duration? o) (xml 'open-close-tag 'duration `((log . ,(ly:duration-log o)) (dots . ,(ly:duration-dot-count o)) (numer . ,(car (ly:duration-factor o))) (denom . ,(cdr (ly:duration-factor o)))))) ((markup-list? o) (markup->lily-xml o xml)) ((and (markup? o) (not (string? o))) (markup->lily-xml o xml)) ((number? o) (xml 'text-tag 'number (number->string o))) ((string? o) (xml 'text-tag 'string o)) ((char? o) (xml 'text-tag 'char (string o))) ((boolean? o) (xml 'text-tag 'boolean (if o "true" "false"))) ((symbol? o) (xml 'text-tag 'symbol (symbol->string o))) ((null? o) (xml 'open-close-tag 'null)) ; or ?? ((list? o) (begin (xml 'open-tag 'list) (for-each (lambda (e) (obj->lily-xml e xml)) o) (xml 'close-tag))) ((pair? o) (begin (xml 'open-tag 'pair) (obj->lily-xml (car o) xml) (obj->lily-xml (cdr o) xml) (xml 'close-tag))) ((procedure? o) (let* ((name (procedure-name o)) (attrs (if name `((name . ,name)) '())) (source (procedure-source o))) (xml 'open-tag 'procedure attrs) (if source (begin (xml 'open-tag 'procedure-source) (obj->lily-xml source xml) (xml 'close-tag))) (xml 'close-tag))) ((ly:stencil? o) (begin (xml 'open-tag 'stencil `((x-min . ,(car (ly:stencil-extent o X))) (x-max . ,(cdr (ly:stencil-extent o X))) (y-min . ,(car (ly:stencil-extent o Y))) (y-max . ,(cdr (ly:stencil-extent o Y))))) (obj->lily-xml (ly:stencil-expr o) xml) (xml 'close-tag))) ((ly:score? o) (begin (xml 'open-tag 'score) (header->lily-xml (ly:score-header o) xml) (obj->lily-xml (ly:score-music o) xml) (xml 'close-tag))) ((ly:book? o) (begin (xml 'open-tag 'book) (header->lily-xml (ly:book-header o) xml) (for-each (lambda (book) (obj->lily-xml book xml)) (reverse (ly:book-book-parts o))) (for-each (lambda (score) (obj->lily-xml score xml)) (reverse (ly:book-scores o))) (xml 'close-tag))) )) #(define-public (xml-export obj) "Dump an XML representation of the specified object to the current output port." (let ((xml (XML))) (xml 'declaration) (obj->lily-xml obj xml))) displayLilyXML = # (define-music-function (parser location music) (ly:music?) "Dump an XML representation of the music to the current output port." (xml-export music) music) python-ly-0.9.3/ly/duration.py0000644000175000017500000000567612500145341017464 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ LilyPond information and logic concerning durations """ from __future__ import unicode_literals from fractions import Fraction durations = [ '\\maxima', '\\longa', '\\breve', '1', '2', '4', '8', '16', '32', '64', '128', '256', '512', '1024', '2048' ] def tostring(dur, dots=0, factor=1): r"""Returns the LilyPond string representation of a given logarithmic duration. Supports values from -3 up to and including 11. -2 = '\longa', 0 = '1' (whole note), etc. Adds the number of dots (defaults to 0) and the fraction factor if given. """ s = durations[dur + 3] + '.' * dots if factor != 1: s += '*{0}'.format(factor) return s def base_scaling(tokens): """Return (base, scaling) as two Fractions for the list of tokens.""" base = Fraction(8, 1 << durations.index(tokens[0])) scaling = Fraction(1) half = base for t in tokens[1:]: if t == '.': half /= 2 base += half elif t.startswith('*'): scaling *= Fraction(t[1:]) return base, scaling def base_scaling_string(duration): """Return (base, scaling) as two Fractions for the specified string.""" items = duration.split('*') dots = items[0].split('.') base = Fraction(8, 1 << durations.index(dots[0].strip())) scaling = Fraction(1) half = base for dot in dots[1:]: half /= 2 base += half for f in items[1:]: scaling *= Fraction(f.strip()) return base, scaling def fraction(tokens): """Return the duration of the Duration tokens as a Fraction.""" base, scaling = base_scaling(tokens) return base * scaling def fraction_string(duration): """Return the duration of the specified string as a Fraction.""" base, scaling = base_scaling_string(duration) return base * scaling def format_fraction(value): """Format the value as "5/1" etc.""" if value == 0: return "0" elif isinstance(value, Fraction): return "{0}/{1}".format(value.numerator, value.denominator) else: return "{0}/1".format(value) python-ly-0.9.3/ly/colorize.py0000644000175000017500000005111012601440456017454 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2013 - 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. """ Classes and functions to colorize (syntax-highlight) parsed source. Highlighting is based on CSS properties and their values, although the Mapping object can map a token's class to any object or value. The Mapping object normally maps a token's class basically to a CSS class and possibly a base CSS class. This way you can define base styles (e.g. string, comment, etc) and have specific classes (e.g. LilyPond string, Scheme comment) inherit from that base style. This CSS class is described by the css_class named tuple, with its three fields: mode, name, base. E.g. ('lilypond', 'articulation', 'keyword'). The base field may be None. The css classes are mapped to dictionaries of css properties, like {'font-weight': 'bold', 'color': '#4800ff'}, etc. A scheme (a collection of styles) is simply a dictionary mapping the mode to a dictionary of CSS dictionaries. The base styles are in the [None] item of the scheme dictionary. """ from __future__ import unicode_literals from __future__ import absolute_import from __future__ import print_function import collections import ly.lex # don't test all the Token base classes _token_mro_slice = slice(1, -len(ly.lex.Token.__mro__)) style = collections.namedtuple("style", "name base classes") css_class = collections.namedtuple("css_class", "mode name base") class Mapper(dict): """Maps token classes to arbitrary values, which can be highlighting styles. Mapper behaves like a dict, you set items with a token class as key to an arbitrary value. But getting items can be done using a token. The token class's method resolution order is walked up and the value for the first available class found in the keys is returned. The class is also cached to speed up requests for other tokens. """ def __getitem__(self, token): cls = type(token) try: return dict.__getitem__(self, cls) except KeyError: for c in cls.__mro__[_token_mro_slice]: try: value = dict.__getitem__(self, c) break except KeyError: pass else: value = None self[cls] = value return value def default_mapping(): """Return a good default mapping from token class(es) to style and default style, per group.""" from ly.lex import lilypond from ly.lex import scheme from ly.lex import html from ly.lex import texinfo #from ly.lex import latex #from ly.lex import docbook return ( ('lilypond', ( style('keyword', 'keyword', (lilypond.Keyword,)), style('command', 'function', (lilypond.Command, lilypond.Skip)), style('pitch', None, (lilypond.MusicItem,)), style('octave', None, (lilypond.Octave,)), style('accidental', None, (lilypond.Accidental, lilypond.FigureAccidental)), style('duration', None, (lilypond.Duration,)), style('dynamic', None, (lilypond.Dynamic,)), style('check', None, (lilypond.OctaveCheck, lilypond.PipeSymbol)), style('articulation', None, (lilypond.Direction, lilypond.Articulation)), style('fingering', None, (lilypond.Fingering,)), style('stringnumber', None, (lilypond.StringNumber,)), style('slur', None, (lilypond.Slur,)), style('beam', None, (lilypond.Beam, lilypond.FigureBracket,)), style('chord', None, (lilypond.Chord, lilypond.ChordItem)), style('markup', 'function', (lilypond.Markup,)), style('lyricmode', 'function', (lilypond.LyricMode,)), style('lyrictext', None, (lilypond.Lyric,)), style('repeat', 'function', (lilypond.Repeat, lilypond.Tremolo,)), style('specifier', 'variable', (lilypond.Specifier,)), style('usercommand', 'variable', (lilypond.UserCommand,)), style('figbass', None, (lilypond.Figure,)), style('figbstep', None, (lilypond.FigureStep,)), style('figbmodif', None, (lilypond.FigureModifier,)), style('delimiter', 'keyword', (lilypond.Delimiter,)), style('context', None, (lilypond.ContextName,)), style('grob', None, (lilypond.GrobName,)), style('property', 'variable', (lilypond.ContextProperty,)), style('variable', 'variable', (lilypond.Variable,)), style('uservariable', None, (lilypond.UserVariable,)), style('value', 'value', (lilypond.Value,)), style('string', 'string', (lilypond.String,)), style('stringescape', 'escape', (lilypond.StringQuoteEscape,)), style('comment', 'comment', (lilypond.Comment,)), style('error', 'error', (lilypond.Error,)), )), ('scheme', ( style('scheme', None, (lilypond.SchemeStart, scheme.Scheme,)), style('string', 'string', (scheme.String,)), style('comment', 'comment', (scheme.Comment,)), style('number', 'value', (scheme.Number,)), style('lilypond', None, (scheme.LilyPond,)), style('keyword', 'keyword', (scheme.Keyword,)), style('function', 'function', (scheme.Function,)), style('variable', 'variable', (scheme.Variable,)), style('constant', 'variable', (scheme.Constant,)), style('delimiter', None, (scheme.OpenParen, scheme.CloseParen,)), )), ('html', ( style('tag', 'keyword', (html.Tag,)), style('attribute', 'variable', (html.AttrName,)), style('value', 'value', (html.Value,)), style('string', 'string', (html.String,)), style('entityref', 'escape', (html.EntityRef,)), style('comment', 'comment', (html.Comment,)), style('lilypondtag', 'function', (html.LilyPondTag,)), )), ('texinfo', ( style('keyword', 'keyword', (texinfo.Keyword,)), style('block', 'function', (texinfo.Block,)), style('attribute', 'variable', (texinfo.Attribute,)), style('escapechar', 'escape', (texinfo.EscapeChar,)), style('verbatim', 'string', (texinfo.Verbatim,)), style('comment', 'comment', (texinfo.Comment,)), )), ) # end of mapping default_scheme = { # the base styles None: { 'keyword': { 'font-weight': 'bold', }, 'function': { 'font-weight': 'bold', 'color': '#0000c0', }, 'variable': { 'color': '#0000ff', }, 'value': { 'color': '#808000', }, 'string': { 'color': '#c00000', }, 'escape': { 'color': '#008080', }, 'comment': { 'color': '#808080', 'font-style': 'italic', }, 'error': { 'color': '#ff0000', 'text-decoration': 'underline', 'text-decoration-color': '#ff0000', }, }, 'lilypond': { 'duration': { 'color': '#008080', }, 'markup': { 'color': '#008000', 'font-weight': 'normal', }, 'lyricmode': { 'color': '#006000', }, 'lyrictext': { 'color': '#006000', }, 'grob': { 'color': '#c000c0', }, 'context': { 'font-weight': 'bold', }, 'slur': { 'font-weight': 'bold', }, 'articulation': { 'font-weight': 'bold', 'color': '#ff8000', }, 'dynamic': { 'font-weight': 'bold', 'color': '#ff8000', }, 'fingering': { 'color': '#ff8000', }, 'stringnumber': { 'color': '#ff8000', }, }, 'scheme': { }, 'html': { }, 'texinfo': { }, } # end of default_css_styles def get_tokens(cursor): """Return the list of tokens for the cursor. Tokens that are partially inside the cursor's selection are re-created so that they fall exactly within the selection. This can be used to convert a highlighted part of a document to e.g. HTML. """ tokens = list(ly.document.Source(cursor, None, ly.document.PARTIAL, True)) if tokens: if cursor.end is not None and tokens[-1].end > cursor.end: t = tokens[-1] tokens[-1] = type(t)(t[:cursor.end - t.end], t.pos) if cursor.start > tokens[0].pos: t = tokens[0] tokens[0] = type(t)(t[cursor.start - t.pos:], cursor.start) return tokens def map_tokens(cursor, mapper): """Yield a two-tuple(token, style) for every token. The style is what mapper[token] returns. Style may be None, which also happens with unparsed (not-tokenized) text. """ text = cursor.document.plaintext() start = cursor.start tokens = get_tokens(cursor) t = None for t in tokens: if t.pos > start: yield text[start:t.pos], None yield t, mapper[t] start = t.end if t and cursor.end is not None and cursor.end > t.end: yield text[t.end:cursor.end] def melt_mapped_tokens(mapped_tokens): """Melt adjacent tokens with the same mapping together.""" prev_tokens = [] prev_style = None for t, s in mapped_tokens: if s == prev_style or t.isspace(): prev_tokens.append(t) else: if prev_tokens: if prev_tokens[-1] == ' ': yield ''.join(prev_tokens[:-1]), prev_style yield ' ', None else: yield ''.join(prev_tokens), prev_style prev_tokens = [t] prev_style = s if prev_tokens: yield ''.join(prev_tokens), prev_style def css_mapper(mapping=None): """Return a Mapper dict, mapping token classes to two CSS classes. By default the mapping returned by default_mapping() is used. """ if mapping is None: mapping = default_mapping() return Mapper((cls, css_class(mode, style.name, style.base)) for mode, styles in mapping for style in styles for cls in style.classes) def css_dict(css_style, scheme=default_scheme): """Return the css properties dict for the style, taken from the scheme. This can be used for inline style attributes. """ d = {} try: d.update(scheme[None][css_style.base]) except KeyError: pass try: d.update(scheme[css_style.mode][css_style.name]) except KeyError: pass return d def css_item(i): """Return "name: value;" where i = (name, value).""" return '{0}: {1};'.format(*i) def css_attr(d): """Return a dictionary with a 'style' key. The value is the style items in d formatted with css_item() joined with spaces. If d is empty, an empty dictionary is returned. """ if d: return {'style': ' '.join(map(css_item, sorted(d.items())))} return {} def css_group(selector, d): """Return a "selector { items...}" part of a CSS stylesheet.""" return '{0} {{\n {1}\n}}\n'.format( selector, '\n '.join(map(css_item, sorted(d.items())))) def format_css_span_class(css_style): """Return a string like 'class="mode-style base"' for the specified style.""" c = css_style.mode + '-' + css_style.name if css_style.base: c += ' ' + css_style.base return 'class="{0}"'.format(c) class css_style_attribute_formatter(object): """Return the inline style attribute for a specified style.""" def __init__(self, scheme=default_scheme): self.scheme = scheme def __call__(self, css_style): d = css_dict(css_style, self.scheme) if d: return 'style="{0}"'.format(' '.join(map(css_item, sorted(d.items())))) def format_stylesheet(scheme=default_scheme): """Return a formatted stylesheet for the stylesheet scheme dictionary.""" sheet = [] key = lambda i: '' if i[0] is None else i[0] for mode, styles in sorted(scheme.items(), key=key): if styles: sheet.append('/* {0} */'.format( "mode: " + mode if mode else "base styles")) for css_class, d in sorted(styles.items()): if mode: selector = 'span.{0}-{1}'.format(mode, css_class) else: selector = '.' + css_class sheet.append(css_group(selector, d)) return '\n'.join(sheet) def html_escape(text): """Escape &, < and >.""" return text.replace('&', '&').replace('<', '<').replace('>', '>') def html_escape_attr(text): """Escape &, ", < and >.""" return html_escape(text).replace('"', '"') def html_format_attrs(d): """Format the attributes dict as a string. The attributes are escaped correctly. A space is prepended for every assignment. """ return ''.join(' {0}="{1}"'.format( k, html_escape_attr(format(v))) for k, v in d.items()) def html(cursor, mapper, span=format_css_span_class): """Return a HTML string with the tokens wrapped in elements. The span argument is a function returning an attribute for the tag for the specified style. By default the format_css_span_class() function is used, that returns a 'class="group style base"' string. You'll want to wrap the HTML inside
 tokens and add a CSS stylesheet.

    """
    result = []
    for t, style in melt_mapped_tokens(map_tokens(cursor, mapper)):
        arg = span(style) if style else None
        if arg:
            result.append(''.format(arg))
            result.append(html_escape(t))
            result.append('')
        else:
            result.append(html_escape(t))
    return ''.join(result)


def add_line_numbers(cursor, html, linenum_attrs=None, document_attrs=None):
    """Combines the html (returned by html()) with the line numbers in a HTML table.

    The linenum_attrs are put in the  tag for the line numbers. The
    default value is: {"style": "background: #eeeeee;"}. The document_attrs
    are put in the  tag for the document. The default is empty.

    By default, the id for the linenumbers  is set to "linenumbers",
    and the id for the document  is set to "document".

    """
    linenum_attrs = dict(linenum_attrs) if linenum_attrs else {"style": "background: #eeeeee;"}
    document_attrs = dict(document_attrs) if document_attrs else {}
    linenum_attrs.setdefault('id', 'linenumbers')
    document_attrs.setdefault('id', 'document')
    linenum_attrs['valign'] = 'top'
    linenum_attrs['align'] = 'right'
    linenum_attrs['style'] = linenum_attrs.get('style', '') + 'vertical-align: top; text-align: right;'
    document_attrs['valign'] = 'top'
    document_attrs['style'] = document_attrs.get('style', '') + 'vertical-align: top;'

    start_num = cursor.document.index(cursor.start_block()) + 1
    end_num = cursor.document.index(cursor.end_block()) + 1
    linenumbers = '
{0}
'.format('\n'.join(map(format, range(start_num, end_num)))) body = '
{0}
'.format(html) return ( '' '' '' '\n{1}\n' '' '' '\n{3}\n' '
\n').format( html_format_attrs(linenum_attrs), linenumbers, html_format_attrs(document_attrs), body) def format_html_document(body, title="", stylesheet=None, stylesheet_ref=None, encoding='UTF-8'): """Return a complete HTML document. The body is put inside body tags unchanged. The title is html-escaped. If stylesheet_ref is given, it is put as a reference in the HTML; if stylesheet is given, it is put verbatim in a \n'.format(stylesheet) return ( '\n' '\n' '{title}\n' '\n' '{css}' '\n' '\n{body}\n\n').format( title = html_escape(title), encoding = encoding, body = body, css = css, ) class HtmlWriter(object): """A do-it-all object to create syntax highlighted HTML. You can set the instance attributes to configure the behaviour in all details. Then call the html(cursor) method to get the HTML. """ fgcolor = None bgcolor = None linenumbers_fgcolor = None linenumbers_bgcolor = "#eeeeee" inline_style = False number_lines = False wrapper_tag = "pre" wrapper_attribute = "id" document_id = "document" linenumbers_id = "linenumbers" title = "" css_scheme = default_scheme css_mapper = None encoding = 'UTF-8' stylesheet_ref = None full_html = True def set_wrapper_attribute(self, attr): """Choose attribute name for wrapper tag""" valid_attrs = ["id", "class"] if attr in valid_attrs: self.wrapper_attribute = attr else: print("Invalid attribute, has to be one of {}".format(valid_attrs)) def set_wrapper_tag(self, tag): """Define the tag to be used for wrapping the content""" valid_tags = ['pre', 'code', 'div'] if tag in valid_tags: self.wrapper_tag = tag else: print("Invalid tag, has to be one of {}".format(valid_tags)) def html(self, cursor): """Return the output HTML.""" doc_style = {} if self.fgcolor: doc_style['color'] = self.fgcolor if self.bgcolor: doc_style['background'] = self.bgcolor num_style = {} if self.linenumbers_fgcolor: num_style['color'] = self.linenumbers_fgcolor if self.linenumbers_bgcolor: num_style['background'] = self.linenumbers_bgcolor num_attrs = {self.wrapper_attribute: self.linenumbers_id} doc_attrs = {self.wrapper_attribute: self.document_id} css = [] if self.inline_style: formatter = css_style_attribute_formatter(self.css_scheme) num_attrs.update(css_attr(num_style)) doc_attrs.update(css_attr(doc_style)) else: formatter = format_css_span_class wrap_type = '#' if self.wrapper_attribute == 'id' else '.' css.append(css_group(wrap_type + self.document_id, doc_style)) if self.number_lines: css.append(css_group(wrap_type + self.linenumbers_id, num_style)) css.append(format_stylesheet(self.css_scheme)) body = html(cursor, self.css_mapper or css_mapper(), formatter) if self.number_lines: body = add_line_numbers(cursor, body, num_attrs, doc_attrs) else: body = '<{0}{1}>{2}'.format(self.wrapper_tag, html_format_attrs(doc_attrs), body) if not self.full_html: return body if self.stylesheet_ref: css = None else: css = '\n'.join(css) return format_html_document(body, self.title, css, self.stylesheet_ref, self.encoding) python-ly-0.9.3/ly/docinfo.py0000644000175000017500000003100712476157645017271 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2013 - 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. """ Harvest information from a ly.document.DocumentBase instance. """ from __future__ import unicode_literals from __future__ import absolute_import import re import collections import functools import itertools import ly.lex.lilypond import ly.pitch def _cache(func): """Simple decorator caching the return value of a function.""" @functools.wraps(func) def wrapper(self): try: return self._cache_[func] except AttributeError: self._cache_ = {} except KeyError: pass result = self._cache_[func] = func(self) return result return wrapper class DocInfo(object): """Harvest information from a ly.document.DocumentBase instance. All tokens are saved in the tokens attribute as a tuple. Newline tokens are added between all lines. All corresponding classes are in the classes attribute as a tuple. This makes quick search and access possible. The tokens are requested from the document using the tokens_with_position() method, so you can always locate them back in the original document using their pos attribute. DocInfo does not update when the document changes, you should just instantiate a new one. """ def __init__(self, doc): """Initialize with ly.document.DocumentBase instance.""" self._d = doc blocks = iter(doc) for b in blocks: tokens = doc.tokens_with_position(b) self.tokens = sum(map( lambda b: ((ly.lex.Newline('\n', doc.position(b) - 1),) + doc.tokens_with_position(b)), blocks), tokens) self.classes = tuple(map(type, self.tokens)) @property def document(self): return self._d def range(self, start=0, end=None): """Return a new instance of the DocInfo class for the selected range. Only the tokens completely contained within the range start..end are added to the new instance. This can be used to perform fast searches on a subset of a document. """ if start == 0 and end is None: return self lo = 0 hi = len(self.tokens) while lo < hi: mid = (lo + hi) // 2 if start > self.tokens[mid].pos: lo = mid + 1 else: hi = mid start = lo if end is not None: lo = 0 hi = len(self.tokens) while lo < hi: mid = (lo + hi) // 2 if end < self.tokens[mid].pos: hi = mid else: lo = mid + 1 end = lo - 1 s = slice(start, end) n = type(self).__new__(type(self)) n._d = self._d n.tokens = self.tokens[s] n.classes = self.classes[s] return n @_cache def mode(self): """Return the mode, e.g. "lilypond".""" return self._d.initial_state().mode() def find(self, token=None, cls=None, pos=0, endpos=-1): """Return the index of the first specified token and/or class after pos. If token is None, the cls should be specified. If cls is given, the token should be an instance of the specified class. If endpos is given, never searches beyond endpos. Returns -1 if the token is not found. """ if token is None: try: return self.classes.index(cls, pos, endpos) except ValueError: return -1 elif cls is None: try: return self.tokens.index(token, pos, endpos) except ValueError: return -1 else: while True: try: i = self.tokens.index(token, pos, endpos) except ValueError: return -1 if cls == self.classes[i]: return i pos = i + 1 def find_all(self, token=None, cls=None, pos=0, endpos=-1): """Yield all indices of the first specified token and/or class after pos. If token is None, the cls should be specified. If cls is given, the token should be an instance of the specified class. If endpos is given, never searches beyond endpos. Returns -1 if the token is not found. """ while True: i = self.find(token, cls, pos, endpos) if i == -1: break yield i pos = i + 1 @_cache def version_string(self): r"""Return the version as a string, e.g. "2.19.8". Looks for the \version LilyPond command. The string is returned without quotes. Returns None if there was no \version command found. """ i = self.find("\\version", ly.lex.lilypond.Keyword) if i != -1: tokens = iter(self.tokens[i+1:i+10]) for t in tokens: if not isinstance(t, (ly.lex.Space, ly.lex.Comment)): if t == '"': pred = lambda t: t != '"' else: pred = lambda t: not isinstance(t, (ly.lex.Space, ly.lex.Comment)) return ''.join(itertools.takewhile(pred, tokens)) @_cache def version(self): """Return the version_string() as a tuple of ints, e.g. (2, 16, 2).""" version = self.version_string() if version: return tuple(map(int, re.findall(r"\d+", version))) return () @_cache def include_args(self): r"""The list of \include command arguments.""" result = [] for i in self.find_all("\\include", ly.lex.lilypond.Keyword): tokens = iter(self.tokens[i+1:i+10]) for token in tokens: if not isinstance(token, (ly.lex.Space, ly.lex.Comment)): if token == '"': result.append(''.join(itertools.takewhile(lambda t: t != '"', tokens))) break return result @_cache def scheme_load_args(self): """The list of scheme (load) command arguments.""" result = [] for i in self.find_all("load", ly.lex.scheme.Keyword): tokens = iter(self.tokens[i+1:i+10]) for token in tokens: if not isinstance(token, (ly.lex.Space, ly.lex.Comment)): if token == '"': result.append(''.join(itertools.takewhile(lambda t: t != '"', tokens))) break return result @_cache def output_args(self): r"""The list of arguments of constructs defining the name of output documents. This looks at the \bookOutputName, \bookOutputSuffix and define output-suffix commands. Every argument is a two tuple(type, argument) where type is either "suffix" or "name". """ result = [] for arg_type, cmd, cls in ( ("suffix", "output-suffix", ly.lex.scheme.Word), ("suffix", "\\bookOutputSuffix", ly.lex.lilypond.Command), ("name", "\\bookOutputName", ly.lex.lilypond.Command), ): for i in self.find_all(cmd, cls): tokens = iter(self.tokens[i+1:i+6]) for t in tokens: if t == '"': arg = ''.join(itertools.takewhile(lambda t: t != '"', tokens)) result.append((arg_type, arg)) break elif isinstance(t, (ly.lex.lilypond.SchemeStart, ly.lex.Space, ly.lex.Comment)): continue break return result @_cache def definitions(self): """The list of LilyPond identifiers the document defines.""" result = [] for i in self.find_all(None, ly.lex.lilypond.Name): if i == 0 or self.tokens[i-1] == '\n': result.append(self.tokens[i]) return result @_cache def markup_definitions(self): """The list of markup command definitions in the document.""" result = [] # find bla = \markup { .. } for i in self.find_all(None, ly.lex.lilypond.Name): if i == 0 or self.tokens[i-1] == '\n': for t in self.tokens[i+1:i+6]: if t == "\\markup": result.append(self.tokens[i]) elif t == "=" or t.isspace(): continue break # find #(define-markup-command construction for i in self.find_all('define-markup-command', ly.lex.scheme.Word): for t in self.tokens[i+1:i+6]: if isinstance(t, ly.lex.scheme.Word): result.append(t) break result.sort(key=lambda t: t.pos) return result @_cache def language(self): """The pitch language, None if not set in the document.""" languages = ly.pitch.pitchInfo.keys() for i in self.find_all("\\language", ly.lex.lilypond.Keyword): for t in self.tokens[i+1:i+10]: if isinstance(t, ly.lex.Space): continue elif t == '"': continue if t in languages: return t for n in self.include_args(): lang = n.rsplit('.', 1)[0] if lang in languages: return lang @_cache def global_staff_size(self): """The global-staff-size, if set, else None.""" i = self.find('set-global-staff-size', ly.lex.scheme.Function) if i != -1: try: return int(self.tokens[i+2]) except (IndexError, ValueError): pass @_cache def token_hash(self): """Return an integer hash for all non-whitespace and non-comment tokens. This hash does not change when only comments or whitespace are changed. """ return hash(tuple(t for t in self.tokens if not isinstance(t, (ly.lex.Space, ly.lex.Comment)))) @_cache def complete(self): """Return whether the document is probably complete and could be compilable.""" return self._d.state_end(self._d[len(self._d)-1]).depth() == 1 @_cache def has_output(self): """Return True when the document probably generates output. I.e. has notes, rests, markup or other output-generating commands. """ for t, c in ( (None, ly.lex.lilypond.MarkupStart), (None, ly.lex.lilypond.Note), (None, ly.lex.lilypond.Rest), ('\\include', ly.lex.lilypond.Keyword), (None, ly.lex.lilypond.LyricMode), ): for i in self.find_all(t, c): return True return False def count_tokens(self, cls): """Return the number of tokens that are (a subclass) of the specified class. If you only want the number of instances of the exact class (not a subclass of) you can use info.classes.count(cls), where info is a DocInfo instance. """ return sum([issubclass(c, cls) for c in self.classes], False) def counted_tokens(self): """Return a dictionary mapping classes to the number of instances of that class.""" return collections.Counter(self.classes) python-ly-0.9.3/ly/document.py0000644000175000017500000010335312602033245017446 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2013 - 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. """ DocumentBase and Document ========================= Represents a LilyPond source document (the text contents). The Document implementation keeps the document in a (unicode) text string, but you can inherit from the DocumentBase class to support other representations of the text content. Modifying is preferably done inside a context (the with statement), e.g.: .. code-block:: python d = Document('some string') with d: d[5:5] = 'different ' d.plaintext() --> 'some different string' Changes are applied when the context is exited, also the modified part of the document is re-tokenized. Changes may not overlap. You may modify the document outside a context, in which case the document is re-tokenized immediately. This is much slower however when performing multiple changes after each other. The tokens(block) method returns a tuple of tokens for the specified block. Depending on the implementation, a block describes a line in the LilyPond source document. It is not expected to have any methods, except that the '==' operator is supported between two blocks, and returns True if both refer to the same line of text in the source document. Cursor ====== Defines a range or position in a Document. Runner ====== A Runner allows iterating back and forth over the tokens of a document. Source ====== Iterate over tokens in a (part of a) Document, with or without state. """ from __future__ import unicode_literals from __future__ import absolute_import import io import sys import operator import collections import weakref import ly.lex class DocumentBase(object): """Abstract base class for Document instances. You should inherit the following methods: setplaintext __len__ __getitem__ block index position text tokens isvalid initial_state state_end apply_changes You may inherit (e.g. to get speed improvements): plaintext next_block previous_block blocks_forward blocks_backward state You may use the following attributes: filename (None) # can represent the filename of the document on disk encoding (None) # can represent the encoding of the document when reading/writing to disk """ filename = None encoding = None def __init__(self): """Constructor""" self._writing = 0 self._changes = collections.defaultdict(list) self._cursors = weakref.WeakSet() def __bool__(self): return True __nonzero__ = __bool__ # py2 compat def __iter__(self): """Iter over all blocks.""" return self.blocks_forward(self[0]) def __len__(self): """Return the number of blocks""" raise NotImplementedError() def __getitem__(self, index): """Return the block at the specified index.""" raise NotImplementedError() def plaintext(self): """The document contents as a plain text string.""" return '\n'.join(map(self.text, self)) def setplaintext(self, text): """Sets the document contents to the text string.""" raise NotImplementedError() def size(self): """Return the number of characters in the document.""" last_block = self[len(self) - 1] return self.position(last_block) + len(self.text(last_block)) def block(self, position): """Return the text block at the specified character position. The text block itself has no methods, but it can be used as an argument to other methods of this class. (Blocks do have to support the '==' operator.) """ raise NotImplementedError() def index(self, block): """Return the linenumber of the block (starting with 0).""" raise NotImplementedError() def blocks_forward(self, block): """Iter forward starting with the specified block.""" while self.isvalid(block): yield block block = self.next_block(block) def blocks_backward(self, block): """Iter backwards starting with the specified block.""" while self.isvalid(block): yield block block = self.previous_block(block) def position(self, block): """Return the position of the specified block.""" raise NotImplementedError() def text(self, block): """Return the text of the specified block.""" raise NotImplementedError() def next_block(self, block): """Return the next block, which may be invalid.""" index = self.index(block) if index < len(self) - 1: return self[index + 1] def previous_block(self, block): """Return the previous block, which may be invalid.""" index = self.index(block) if index > 0: return self[index - 1] def isvalid(self, block): """Return True if the block is a valid block.""" raise NotImplementedError() def isblank(self, block): """Return True if the block is empty or blank.""" t = self.text(block) return not t or t.isspace() def __enter__(self): """Start the context for modifying the document.""" self._writing += 1 return self def __exit__(self, exc_type, exc_val, exc_tb): """Exit the context for modifying.""" if exc_type is not None: # cancel all edits when an exception occurred self._writing = 0 self._changes.clear() elif self._writing == 1: if self._changes: self._sort_changes() self.update_cursors() self.apply_changes() del self._changes_list self._writing = 0 elif self._writing > 1: self._writing -= 1 def _register_cursor(self, cursor): """Make a weak reference to the cursor. This is called by the constructor of the Cursor. The Cursor gets updated when the document is changed. """ self._cursors.add(cursor) def check_changes(self): """Debugging method that checks for overlapping edits.""" pos = self.size() for start, end, text in self._changes_list: if end > pos: if len(text) > 12: text = text[:10] + '...' raise ValueError("overlapping edit: {0}-{1}: {2}".format(start, end, text)) pos = start def _sort_changes(self): """Sort all the changes and put them in the _changes_list.""" self._changes_list = [(start, end, text) for start, items in sorted(self._changes.items(), reverse=True) for end, text in reversed(sorted(items, key=lambda i: (i[0] is None, i[0])))] self._changes.clear() def update_cursors(self): """Updates the position of the registered Cursor instances.""" for start, end, text in self._changes_list: for c in self._cursors: if c.start > start: if end is None or end >= c.start: c.start = start else: c.start += start + len(text) - end if c.end is not None and c.end >= start: if end is None or end >= c.end: c.end = start + len(text) else: c.end += start + len(text) - end def apply_changes(self): """Apply the changes and update the tokens.""" raise NotImplementedError() def tokens(self, block): """Return the tuple of tokens of the specified block. The pos and end attributes of every token point to the position of the token in the block. """ raise NotImplementedError() def tokens_with_position(self, block): """Return a tuple of tokens of the specified block. The pos and end attributes of every token point to the position in the Document, instead of to the position in the current block. This makes it easier to iterate over tokens and change the document. """ pos = self.position(block) return tuple(type(t)(t, pos + t.pos) for t in self.tokens(block)) def initial_state(self): """Return the state at the beginning of the document.""" raise NotImplementedError() def state(self, block): """Return the state at the start of the specified block.""" prev = self.previous_block(block) if self.isvalid(prev): return self.state_end(prev) return self.initial_state() def state_end(self, block): """Return the state at the end of the specified block.""" raise NotImplementedError() def __setitem__(self, key, text): """Change the text pointed to in key (integer or slice). If start > stop in the slice (and stop is not None), start and stop are swapped. (This is different than usual Python behaviour, where stop is set to start if it was lower.) """ if isinstance(key, slice): start = key.start or 0 end = key.stop if end is not None and start > end: start, end = end, start else: start = key end = start + 1 text = text.replace('\r', '') if text or start != end: self._changes[start].append((end, text)) # when a change is made outside context manager, apply immediately if self._writing == 0: self._sort_changes() self.update_cursors() self.apply_changes() del self._changes_list def __delitem__(self, key): """Remove the range of text.""" self[key] = "" class Document(DocumentBase): """A plain text LilyPond source document that auto-updates the tokens. The modified attribute is set to True as soon as the document is changed, but the setplaintext() method sets it to False. """ modified = False def __init__(self, text='', mode=None): super(Document, self).__init__() self._fridge = ly.lex.Fridge() self._mode = mode self._guessed_mode = None self.setplaintext(text) @classmethod def load(cls, filename, encoding='utf-8', mode=None): """Load the document from a file, using the specified encoding and mode.""" with io.open(filename, encoding=encoding) as f: doc = cls(f.read(), mode) doc.filename = filename return doc def __len__(self): """Return the number of blocks""" return len(self._blocks) def __getitem__(self, index): """Return the block at the specified index.""" return self._blocks[index] def setmode(self, mode): """Sets the mode to one of the ly.lex modes. Use None to auto-determine the mode. """ if mode not in ly.lex.modes: mode = None if mode == self._mode: return self._mode, old_mode = mode, self._mode if not mode: self._guessed_mode = ly.lex.guessMode(self.plaintext()) if self._guessed_mode == old_mode: return elif not old_mode: if mode == self._guessed_mode: return self._update_all_tokens() def mode(self): """Return the mode (lilypond, html, etc). None means automatic mode.""" return self._mode def setplaintext(self, text): """Set the text of the document, sets modified to False.""" text = text.replace('\r', '') lines = text.split('\n') self._blocks = [_Block(t, n) for n, t in enumerate(lines)] pos = 0 for b in self._blocks: b.position = pos pos += len(b.text) + 1 if not self._mode: self._guessed_mode = ly.lex.guessMode(text) self._update_all_tokens() self.modified = False def _update_all_tokens(self): state = self.initial_state() for b in self._blocks: b.tokens = tuple(state.tokens(b.text)) b.state = self._fridge.freeze(state) def initial_state(self): """Return the state at the beginning of the document.""" return ly.lex.state(self._mode or self._guessed_mode) def state_end(self, block): """Return the state at the end of the specified block.""" return self._fridge.thaw(block.state) def block(self, position): """Return the text block at the specified character position.""" if 0 <= position <= self._blocks[-1].position + len(self._blocks[-1].text): lo = 0 hi = len(self._blocks) while lo < hi: mid = (lo + hi) // 2 if position < self._blocks[mid].position: hi = mid else: lo = mid + 1 return self._blocks[lo-1] def index(self, block): """Return the linenumber of the block (starting with 0).""" return block.index def position(self, block): """Return the position of the specified block.""" return block.position def text(self, block): """Return the text of the specified block.""" return block.text def isvalid(self, block): """Return True if the block is a valid block.""" return bool(block) def tokens(self, block): """Return the tuple of tokens of the specified block.""" return block.tokens def apply_changes(self): for start, end, text in self._changes_list: s = self.block(start) # first remove the old contents if end is None: # all text to the end should be removed s.text = s.text[:start - s.position] del self._blocks[s.index+1:] else: # remove until the end position e = self.block(end) s.text = s.text[:start - s.position] + e.text[end - e.position:] del self._blocks[s.index+1:e.index+1] # now insert the new stuff if text: lines = text.split('\n') lines[-1] += s.text[start - s.position:] s.text = s.text[:start - s.position] + lines[0] self._blocks[s.index+1:s.index+1] = map(_Block, lines[1:]) # make sure this line gets reparsed s.tokens = None # update the position of all the new blocks pos = s.position for i, b in enumerate(self._blocks[s.index:], s.index): b.index = i b.position = pos pos += len(b.text) + 1 self.modified = True # if the initial state has changed, reparse everything if not self._mode: mode = ly.lex.guessMode(self.plaintext()) if mode != self._guessed_mode: self._guessed_mode = mode self._update_all_tokens() return # update the tokens starting at block s state = self.state(s) reparse = False for block in self._blocks[s.index:]: if reparse or block.tokens is None: block.tokens = tuple(state.tokens(block.text)) frozen = self._fridge.freeze(state) reparse = block.state != frozen block.state = frozen else: state = self._fridge.thaw(block.state) class _Block(object): """A line of text. This class is only used by the Document implementation. """ position = sys.maxsize # prevent picking those blocks before updating pos state = None tokens = None def __init__(self, text="", index=-1): self.text = text self.index = index class Cursor(object): """Defines a certain range (selection) in a Document. You may change the start and end attributes yourself. Both must be an integer, end may also be None, denoting the end of the document. As long as you keep a reference to the Cursor, its positions are updated when the document changes. When text is inserted at the start position, it remains the same. But when text is inserted at the end of a cursor, the end position moves along with the new text. E.g.: .. code-block:: python d = Document('hi there, folks!') c = Cursor(d, 8, 8) with d: d[8:8] = 'new text' c.start, c.end --> (8, 16) Many tools in the ly module use this object to describe (part of) a document. """ def __init__(self, doc, start=0, end=None): self._d = doc self.start = start self.end = end doc._register_cursor(self) @property def document(self): return self._d def start_block(self): """Return the block the start attribute points at.""" return self._d.block(self.start) def end_block(self): """Return the block the end attribute points at.""" if self.end is None: return self._d[len(self._d)-1] return self._d.block(self.end) def blocks(self): """Iterate over the selected blocks. If there are multiple blocks and the cursor ends on the first position of the last selected block, that block is not included. """ if self.end == self.start: yield self.start_block() else: for b in self._d.blocks_forward(self.start_block()): if self.end is not None and self._d.position(b) >= self.end: break yield b def text(self): """Convenience method to return the selected text.""" return self._d.plaintext()[self.start:self.end] def text_before(self): """Return text before the cursor in it's start block.""" b = self.start_block() pos = self.start - self._d.position(b) return self._d.text(b)[:pos] def text_after(self): """Return text after the cursor in it's end block.""" if self.end is None: return "" b = self.end_block() pos = self.end - self._d.position(b) return self._d.text(b)[pos:] def has_selection(self): """Return True when there is some text selected.""" end = self.end if end is None: end = self._d.size() return self.start != end def select_all(self): """Select all text.""" self.start, self.end = 0, None def select_end_of_block(self): """Move end to the end of the block.""" if self.end is not None: end = self.end_block() self.end = self._d.position(end) + len(self._d.text(end)) def select_start_of_block(self): """Move start to the start of the block.""" start = self.start_block() self.start = self._d.position(start) def lstrip(self, chars=None): """Move start to the right, like Python's lstrip() string method.""" if self.has_selection(): text = self.text() self.start += len(text) - len(text.lstrip(chars)) def rstrip(self, chars=None): """Move end to the left, like Python's lstrip() string method.""" if self.has_selection(): text = self.text() end = self._d.size() if self.end is None else self.end end -= len(text) - len(text.rstrip(chars)) if end < self._d.size(): self.end = end def strip(self, chars=None): """Strip chars from the selection, like Python's strip() method.""" self.rstrip(chars) self.lstrip(chars) class Runner(object): """Iterates back and forth over tokens. A Runner can stop anywhere and remembers its current token. """ def __init__(self, doc, tokens_with_position=False): """Create and init with Document. If tokens_with_position is True, uses the tokens_with_position() method to get the tokens, else (by default), the tokens() method is used. The Runner is initialized at position 0. Alternatively, you can use the 'at' classmethod to construct a Runner at a specific cursor position. """ self._doc = doc self._wp = tokens_with_position self.move_to_block(doc[0]) @classmethod def at(cls, cursor, after_token=False, tokens_with_position=False): """Create and init from a Cursor. The Runner is positioned so that yielding forward starts with the first complete token after the cursor's start position. Set after_token to True if you want to position the cursor after the token, so that it gets yielded when you go backward. If tokens_with_position is True, uses the tokens_with_position() method to get the tokens, else (by default), the tokens() method is used. """ runner = cls(cursor.document, tokens_with_position) runner.set_position(cursor.start, after_token) return runner @property def document(self): """Return our Document.""" return self._doc def set_position(self, position, after_token=False): """Positions the Runner at the specified position. Set after_token to True if you want to position the cursor after the token, so that it gets yielded when you go backward. """ block = self._doc.block(position) self.move_to_block(block) if after_token: for t in self.forward_line(): if self.position() + len(t) >= position: self._index += 1 break else: for t in self.forward_line(): if self.position() + len(t) > position: self._index -= 1 break def move_to_block(self, block, at_end=False): """Positions the Runner at the start of the given text block. If at_end == True, the iterator is positioned past the end of the block. """ if self._doc.isvalid(block): self.block = block method = self._doc.tokens_with_position if self._wp else self._doc.tokens self._tokens = method(block) self._index = len(self._tokens) if at_end else -1 return True def _newline(self): """(Internal) Create a Newline token at the end of the current block.""" pos = len(self._doc.text(self.block)) if self._wp: pos += self._doc.position(self.block) return ly.lex.Newline('\n', pos) def forward_line(self): """Yields tokens in forward direction in the current block.""" end = len(self._tokens) if self._index < end: while True: self._index += 1 if self._index == end: break yield self._tokens[self._index] def forward(self): """Yields tokens in forward direction across blocks.""" while True: for t in self.forward_line(): yield t newline = self._newline() if not self.next_block(): break yield newline def backward_line(self): """Yields tokens in backward direction in the current block.""" if self._index >= 0: while True: self._index -= 1 if self._index == -1: break yield self._tokens[self._index] def backward(self): """Yields tokens in backward direction across blocks.""" while True: for t in self.backward_line(): yield t if not self.previous_block(): break yield self._newline() def previous_block(self, at_end=True): """Go to the previous block, positioning the cursor at the end by default. Returns False if there was no previous block, else True. """ return self.move_to_block(self._doc.previous_block(self.block), at_end) def next_block(self, at_end=False): """Go to the next block, positioning the cursor at the start by default. Returns False if there was no next block, else True. """ return self.move_to_block(self._doc.next_block(self.block), at_end) def token(self): """Re-returns the last yielded token.""" if self._tokens: index = self._index if index < 0: index = 0 elif index >= len(self._tokens): index = len(self._tokens) - 1 return self._tokens[index] def position(self): """Returns the position of the current token.""" if self._tokens: pos = self.token().pos if not self._wp: pos += self._doc.position(self.block) return pos else: return self._d.position(self.block) def copy(self): """Return a new Runner at the current position.""" obj = type(self)(self._doc, self._wp) obj.block = self.block obj._tokens = self._tokens obj._index = self._index return obj OUTSIDE = -1 PARTIAL = 0 INSIDE = 1 class Source(object): """Helper iterator. Iterates over the (block, tokens) tuples from a Document (or a part thereof). Stores the current block in the block attribute and the tokens (which also is a generator) in the tokens attribute. Iterating over the source object itself just yields the tokens, while the block attribute contains the current block. You can also iterate over the tokens attribute, which will yield the remaining tokens of the current block and then stop. If you specify a state, the tokens will update the state. If you specify state = True, the state will be taken from the document. """ def __init__(self, cursor, state=None, partial=INSIDE, tokens_with_position=False): """Initialize the iterator. cursor is a Cursor instance, describing a Document and a selected range state is, if given, a ly.lex.State instance or True (in which case the state is taken from the document). The following keyword arguments can be used: partial is either OUTSIDE, PARTIAL, or INSIDE: OUTSIDE: tokens that touch the selected range are also yielded PARTIAL: tokens that overlap the start or end positions are yielded INSIDE: (default) yield only tokens fully contained in the range The partial argument only makes sense if start or end are specified. If tokens_with_position is True, uses the document.tokens_with_position() method to get the tokens from the cursor's document, else (by default), the document.tokens() method is used. """ self._pushback = False self._last = None self._doc = document = cursor.document start_block = document.block(cursor.start) self._wp = tokens_with_position tokens_method = document.tokens_with_position if tokens_with_position else document.tokens # start, end predicates start_pred, end_pred = { OUTSIDE: ( lambda t: t.end < start_pos, lambda t: t.pos > end_pos, ), PARTIAL: ( lambda t: t.end <= start_pos, lambda t: t.pos >= end_pos, ), INSIDE: ( lambda t: t.pos < start_pos, lambda t: t.end > end_pos, ), }[partial] # if a state is given, use it (True: pick state from doc) if state: if state is True: state = document.state(start_block) def token_source(block): for t in tokens_method(block): state.follow(t) yield t else: def token_source(block): return iter(tokens_method(block)) self.state = state # where to start if cursor.start: start_pos = cursor.start if not tokens_with_position: start_pos -= document.position(start_block) # token source for first block def source_start(block): source = token_source(block) for t in source: if not start_pred(t): yield t for t in source: yield t else: source_start = token_source # where to end if cursor.end is not None: end_block = cursor.end_block() end_pos = cursor.end if not tokens_with_position: end_pos -= document.position(end_block) def source_end(source): for t in source: if end_pred(t): break yield t # generate the tokens def generator(): source = source_start block = start_block if cursor.end is not None: while block != end_block: yield block, source(block) source = token_source block = document.next_block(block) yield block, source_end(source(block)) else: for block in document.blocks_forward(start_block): yield block, source(block) source = token_source gen = generator() if tokens_with_position: def newline(): pos = document.position(self.block) - 1 return ly.lex.Newline('\n', pos) else: def newline(): pos = len(document.text(document.previous_block(self.block))) return ly.lex.Newline('\n', pos) # initialize block and tokens for self.block, self.tokens in gen: break # keep them going after the first line def g(): for t in self.tokens: yield t for self.block, self.tokens in gen: yield newline() for t in self.tokens: yield t self._gen = g() def __iter__(self): return self def __next__(self): if self._pushback: self._pushback = False return self._last i = self._last = next(self._gen) return i next = __next__ def pushback(self, pushback=True): """Yields the last yielded token again on the next request. This can be called multiple times, but only the last token will be yielded again. You can also undo a call to pushback() using pushback(False). """ self._pushback = pushback def token(self): """Re-returns the last yielded token.""" return self._last @property def document(self): """Return our Document.""" return self._doc def position(self, token): """Returns the position of the token in the current block. If the iterator was instantiated with tokens_with_position == True, this position is the same as the token.pos attribute, and the current block does not matter. (In that case you'll probably not use this method.) """ pos = token.pos if not self._wp: pos += self._doc.position(self.block) return pos def until_parser_end(self): """Yield the tokens until the current parser is quit. You can only use this method if you have a State enabled. """ depth = self.state.depth() for t in self: yield t if self.state.depth() < depth and not self._pushback: break def consume(self, iterable, position): """Consumes iterable (supposed to be reading from us) until position. Returns the last token if that overlaps position. """ if self._doc.position(self.block) < position: for t in iterable: pos = self.position(t) end = pos + len(t) if end == position: return elif end > position: return t python-ly-0.9.3/ly/rhythm.py0000644000175000017500000003245512636736036017166 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2011 - 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. """ Implementation of the tools to edit durations of selected music. Durations are represented simply by lists of ly.lex.lilypond.Duration tokens. All functions expect a ly.document.Cursor with the selected range. """ from __future__ import unicode_literals import collections import itertools import ly.document import ly.lex.lilypond durations = ['\\maxima', '\\longa', '\\breve', '1', '2', '4', '8', '16', '32', '64', '128', '256', '512', '1024', '2048'] def remove_dups(iterable): """Change reoccurring strings to '' in iterable.""" old = None for i in iterable: yield '' if i == old else i old = i # decribes a musical item that has a duration music_item = collections.namedtuple('music_item', ( 'tokens', # tokens of the item 'dur_tokens', # Duration tokens of the item 'may_remove', # whether the duration may be removed 'insert_pos', # where a Duration could be inserted 'pos', # position of the first token 'end', # end position of the last token )) _start = ( ly.lex.lilypond.Rest, ly.lex.lilypond.Skip, ly.lex.lilypond.Note, ly.lex.lilypond.ChordEnd, ly.lex.lilypond.Q, ly.lex.lilypond.Octave, ly.lex.lilypond.Accidental, ly.lex.lilypond.OctaveCheck, ly.lex.lilypond.Duration, ly.lex.lilypond.Tempo ) _stay = ( ly.lex.lilypond.Octave, ly.lex.lilypond.Accidental, ly.lex.lilypond.OctaveCheck, ly.lex.lilypond.Duration, ly.lex.lilypond.Tie, ) def music_tokens(source, command=False, chord=False): r"""DEPRECATED. Yield lists of tokens describing rests, skips or pitches. source is a ly.document.Source instance following the state. The following keyword arguments can be used: - command: whether to allow pitches in \\relative, \\transpose, etc. - chord: whether to allow pitches inside chords. This function is deprecated and will be removed. You should use music_items() instead. """ skip_parsers = () if not command: skip_parsers += (ly.lex.lilypond.ParsePitchCommand,) if not chord: skip_parsers += (ly.lex.lilypond.ParseChord,) for token in source: if isinstance(source.state.parser(), skip_parsers): continue # make sure to skip the duration tokens in a \tuplet command if token == '\\tuplet': for token in source: if isinstance(token, ly.lex.lilypond.Duration): for token in source: if not isinstance(token, ly.lex.lilypond.Duration): break break elif not isinstance(token, (ly.lex.Space, ly.lex.Numeric)): break while isinstance(token, _start): l = [token] for token in source: if isinstance(token, ly.lex.Space): continue if not isinstance(token, _stay): yield l break l.append(token) else: yield l break def music_items(cursor, command=False, chord=False, partial=ly.document.INSIDE): r"""Yield music_item instances describing rests, skips or pitches. cursor is a ly.document.Cursor instance. The following keyword arguments can be used: - command: whether to allow pitches in \\relative, \\transpose, etc. - chord: whether to allow pitches inside chords. - partial: ly.document.INSIDE (default), PARTIAL or OUTSIDE. See the documentation of ly.document.Source.__init__(). """ skip_parsers = () if not command: skip_parsers += (ly.lex.lilypond.ParsePitchCommand,) if not chord: skip_parsers += (ly.lex.lilypond.ParseChord,) source = ly.document.Source(cursor, True, partial=partial, tokens_with_position=True) def mk_item(l): """Convert a list of tokens to a music_item instance.""" tokens = [] dur_tokens = [] pos = l[0].pos end = l[-1].end for t in l: if isinstance(t, ly.lex.lilypond.Duration): dur_tokens.append(t) else: tokens.append(t) may_remove = not any(map(('\\skip', '\\tempo', '\\tuplet').__contains__, tokens)) if dur_tokens: insert_pos = dur_tokens[0].pos else: for t in reversed(tokens): if not isinstance(t, ly.lex.lilypond.Tie): break insert_pos = t.end return music_item(tokens, dur_tokens, may_remove, insert_pos, pos, end) for token in source: if isinstance(source.state.parser(), skip_parsers): continue # make sure to skip the duration tokens in a \tuplet command if token == '\\tuplet': l = [token] for token in source: if isinstance(token, ly.lex.lilypond.Duration): l.append(token) for token in source: if not isinstance(token, ly.lex.lilypond.Duration): break l.append(token) break elif isinstance(token, ly.lex.Numeric): l.append(token) elif not isinstance(token, ly.lex.Space): break yield mk_item(l) length_seen = False while isinstance(token, _start): l = [token] if isinstance(token, ly.lex.lilypond.Length): length_seen = True for token in source: if isinstance(token, ly.lex.lilypond.Length): if length_seen is True: yield mk_item(l) length_seen = False break else: length_seen = True elif isinstance(token, ly.lex.Space): continue elif not isinstance(token, _stay): yield mk_item(l) length_seen = False break l.append(token) else: yield mk_item(l) break def preceding_duration(cursor): """Return a preceding duration before the cursor, or an empty list.""" tokens = ly.document.Runner.at(cursor).backward() for t in tokens: if isinstance(t, ly.lex.lilypond.Duration): l = [t] for t in tokens: if isinstance(t, ly.lex.lilypond.Duration): l.append(t) elif not isinstance(t, ly.lex.Space): break l.reverse() return l return [] def rhythm_double(cursor): """Doubles all duration values.""" with cursor.document as d: for item in music_items(cursor): for token in item.dur_tokens: if isinstance(token, ly.lex.lilypond.Length): try: i = durations.index(token) except ValueError: pass else: if i > 0: d[token.pos:token.end] = durations[i - 1] break def rhythm_halve(cursor): """Halves all duration values.""" with cursor.document as d: for item in music_items(cursor): for token in item.dur_tokens: if isinstance(token, ly.lex.lilypond.Length): try: i = durations.index(token) except ValueError: pass else: if i < len(durations) - 1: d[token.pos:token.end] = durations[i + 1] break def rhythm_dot(cursor): """Add a dot to all durations.""" with cursor.document as d: for item in music_items(cursor): for token in item.dur_tokens: if isinstance(token, ly.lex.lilypond.Length): d[token.end:token.end] = "." break def rhythm_undot(cursor): """Remove one dot from all durations.""" with cursor.document as d: for item in music_items(cursor): for token in item.dur_tokens: if isinstance(token, ly.lex.lilypond.Dot): del d[token.pos:token.end] break def rhythm_remove_scaling(cursor): """Remove the scaling (like ``*3``, ``*1/3``) from all durations.""" with cursor.document as d: for item in music_items(cursor): for token in item.dur_tokens: if isinstance(token, ly.lex.lilypond.Scaling): del d[token.pos:token.end] def rhythm_remove_fraction_scaling(cursor): """Remove the scaling containing fractions (like ``*1/3``) from all durations.""" with cursor.document as d: for item in music_items(cursor): for token in item.dur_tokens: if isinstance(token, ly.lex.lilypond.Scaling) and '/' in token: del d[token.pos:token.end] def rhythm_remove(cursor): """Remove all durations.""" with cursor.document as d: for item in music_items(cursor): if item.dur_tokens and item.may_remove: del d[item.dur_tokens[0].pos:item.dur_tokens[-1].end] def rhythm_implicit(cursor): """Remove reoccurring durations.""" items = music_items(cursor) for item in items: break else: return prev = item.dur_tokens or preceding_duration(cursor) with cursor.document as d: for item in items: if '\\tempo' not in item.tokens and '\\tuplet' not in item.tokens: if item.dur_tokens: if item.dur_tokens == prev and item.may_remove: del d[item.dur_tokens[0].pos:item.dur_tokens[-1].end] prev = item.dur_tokens def rhythm_implicit_per_line(cursor): """Remove reoccurring durations, but always write one on a new line.""" items = music_items(cursor) for item in items: break else: return prev = item.dur_tokens or preceding_duration(cursor) previous_block = cursor.document.block(prev[0].pos) with cursor.document as d: for item in items: if '\\tempo' not in item.tokens and '\\tuplet' not in item.tokens: block = d.block( (item.dur_tokens or item.tokens) [0].pos) if block != previous_block: if not item.dur_tokens: d[item.insert_pos:item.insert_pos] = ''.join(prev) else: prev = item.dur_tokens previous_block = block elif item.dur_tokens: if item.dur_tokens == prev and item.may_remove: del d[item.dur_tokens[0].pos:item.dur_tokens[-1].end] prev = item.dur_tokens def rhythm_explicit(cursor): """Make all durations explicit.""" items = music_items(cursor) for item in items: break else: return prev = item.dur_tokens or preceding_duration(cursor) with cursor.document as d: for item in items: if '\\tempo' not in item.tokens and '\\tuplet' not in item.tokens: if item.dur_tokens: prev = item.dur_tokens else: d[item.insert_pos:item.insert_pos] = ''.join(prev) def rhythm_overwrite(cursor, durations): """Apply a list of durations to the cursor's range. The durations list looks like ["4", "8", "", "16.",] etc. """ durations_source = remove_dups(itertools.cycle(durations)) with cursor.document as d: for item in music_items(cursor): pos = item.insert_pos end = item.dur_tokens[-1].end if item.dur_tokens else pos d[pos:end] = next(durations_source) def rhythm_extract(cursor): """Return a list of the durations from the cursor's range.""" source = ly.document.Source(cursor, True) durations = [] for item in music_items(cursor): tokens = item.dur_tokens + [t for t in item.tokens if isinstance(t, ly.lex.lilypond.Tie)] durations.append(tokens) # if the first duration was not given, find it if durations and not durations[0]: durations[0] = preceding_duration(cursor) or ['4'] return ["".join(tokens) for tokens in durations] python-ly-0.9.3/ly/data/0000775000175000017500000000000012636745216016203 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/data/__init__.py0000644000175000017500000000725312461641363020313 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2011 - 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. """ Query functions to get data from the LilyPond-generated _data.py module. """ def grob_properties(grob): """Returns the list of properties the named grob supports.""" from . import _data return sorted(set(prop for iface in _data.grobs.get(grob, []) for prop in _data.interfaces[iface])) def grob_properties_with_interface(grob): """Returns a list of two-tuples (property, interface).""" from . import _data return sorted( (prop, iface) for iface in _data.grobs.get(grob, []) for prop in _data.interfaces[iface]) def grob_interfaces(grob, prop=None): """Returns the list of interfaces a grob supports. If prop is given, only returns the interfaces that define prop. """ from . import _data ifaces = _data.grobs.get(grob, []) if prop is None: return ifaces return [iface for iface in ifaces if prop in grob_interface_properties(iface)] def grob_interface_properties(iface): """Returns the list of properties an interface supports.""" from . import _data return _data.interfaces.get(iface, []) def grob_interfaces_for_property(prop): """Returns the list of interfaces that define the property. Most times returns one, but several interface names may be returned. """ from . import _data return [iface for iface, props in _data.interfaces.items() if prop in props] def grobs(): """Returns the sorted list of all grob names.""" from . import _data return sorted(_data.grobs.keys()) def all_grob_properties(): """Returns the list of all properties.""" from . import _data return sorted(set(sum(_data.interfaces.values(), []))) def context_properties(): """Returns the list of context properties.""" from . import _data return _data.contextproperties def engravers(): """Returns the list of engravers and performers.""" from . import _data return _data.engravers def music_glyphs(): """Returns the list of glyphs in the emmentaler font.""" from . import _data return _data.musicglyphs def scheme_keywords(): """Returns the list of guile keywords.""" from . import _data return _data.scheme_keywords def scheme_functions(): """Returns the list of scheme functions.""" from . import _data return _data.scheme_functions def scheme_variables(): """Returns the list of scheme variables.""" from . import _data return _data.scheme_variables def scheme_constants(): """Returns the list of scheme constants.""" from . import _data return _data.scheme_constants def all_scheme_words(): """Returns the list of all scheme words.""" from . import _data return _data.scheme_keywords + _data.scheme_functions \ + _data.scheme_variables + _data.scheme_constants python-ly-0.9.3/ly/data/_scheme_data.py0000644000175000017500000020712112501542536021141 0ustar wilbertwilbert00000000000000#generated by makeschemedata.py version="2.18" scheme_keywords = [ '*', '+', '-', '->char-set', '/', '1+', '1-', '<', '<=', '=', '==', '>', '>=', 'abs', 'access?', 'acons', 'acos', 'acosh', 'activate-readline', 'add-duration', 'add-duration!', 'add-hook!', 'alist->hash-table', 'alist-cons', 'alist-copy', 'alist-delete', 'alist-delete!', 'and', 'and-let*', 'angle', 'any', 'any->c32vector', 'any->c64vector', 'any->f32vector', 'any->f64vector', 'any->s16vector', 'any->s32vector', 'any->s64vector', 'any->s8vector', 'any->u16vector', 'any->u32vector', 'any->u64vector', 'any->u8vector', 'any-bits-set?', 'append', 'append!', 'append-map', 'append-map!', 'append-reverse', 'append-reverse!', 'apply', 'apply:nconc2last', 'apropos-completion-function', 'arithmetic-shift', 'array->list', 'array-contents', 'array-copy!', 'array-copy-in-order!', 'array-dimensions', 'array-equal?', 'array-fill!', 'array-for-each', 'array-in-bounds?', 'array-index-map!', 'array-map!', 'array-map-in-order!', 'array-rank', 'array-ref', 'array-set!', 'array-shape', 'array-type', 'array?', 'ash', 'asin', 'asinh', 'assoc', 'assoc-ref', 'assoc-remove!', 'assoc-set!', 'assq', 'assq-ref', 'assq-remove!', 'assq-set!', 'assv', 'assv-ref', 'assv-remove!', 'assv-set!', 'atan', 'atanh', 'basename', 'begin', 'bit-count', 'bit-count*', 'bit-extract', 'bit-field', 'bit-invert!', 'bit-position', 'bit-set*!', 'bit-set?', 'bitvector', 'bitvector->list', 'bitvector-fill!', 'bitvector-length', 'bitvector-ref', 'bitvector-set!', 'bitvector?', 'bitwise-and', 'bitwise-if', 'bitwise-ior', 'bitwise-merge', 'bitwise-not', 'bitwise-xor', 'boolean?', 'booleans->integer', 'break', 'break!', 'broadcast-condition-variable', 'c32vector', 'c32vector->list', 'c32vector-length', 'c32vector-ref', 'c32vector-set!', 'c32vector?', 'c64vector', 'c64vector->list', 'c64vector-length', 'c64vector-ref', 'c64vector-set!', 'c64vector?', 'caaaar', 'caaadr', 'caaar', 'caadar', 'caaddr', 'caadr', 'caar', 'cadaar', 'cadadr', 'cadar', 'caddar', 'cadddr', 'caddr', 'cadr', 'call-with-input-file', 'call-with-input-string', 'call-with-output-file', 'call-with-output-string', 'call-with-values', 'car', 'car+cdr', 'case', 'case-lambda', 'catch', 'cd', 'cdaaar', 'cdaadr', 'cdaar', 'cdadar', 'cdaddr', 'cdadr', 'cdar', 'cddaar', 'cddadr', 'cddar', 'cdddar', 'cddddr', 'cdddr', 'cddr', 'cdr', 'ceiling', 'char->integer', 'char-alphabetic?', 'char-ci<=?', 'char-ci=?', 'char-ci>?', 'char-downcase', 'char-is-both?', 'char-lower-case?', 'char-numeric?', 'char-ready?', 'char-set', 'char-set->list', 'char-set->string', 'char-set-adjoin', 'char-set-adjoin!', 'char-set-any', 'char-set-complement', 'char-set-complement!', 'char-set-contains?', 'char-set-copy', 'char-set-count', 'char-set-cursor', 'char-set-cursor-next', 'char-set-delete', 'char-set-delete!', 'char-set-diff+intersection', 'char-set-diff+intersection!', 'char-set-difference', 'char-set-difference!', 'char-set-every', 'char-set-filter', 'char-set-filter!', 'char-set-fold', 'char-set-for-each', 'char-set-hash', 'char-set-intersection', 'char-set-intersection!', 'char-set-map', 'char-set-ref', 'char-set-size', 'char-set-unfold', 'char-set-unfold!', 'char-set-union', 'char-set-union!', 'char-set-xor', 'char-set-xor!', 'char-set<=', 'char-set=', 'char-set?', 'char-upcase', 'char-upper-case?', 'char-whitespace?', 'char<=?', 'char=?', 'char>?', 'char?', 'chdir', 'chmod', 'chown', 'chroot', 'circular-list', 'circular-list?', 'close', 'close-fdes', 'close-input-port', 'close-output-port', 'close-pipe', 'close-port', 'closedir', 'closure?', 'command-line', 'complex?', 'concatenate', 'concatenate!', 'cond', 'cond-expand', 'condition', 'condition-has-type?', 'condition-message', 'condition-ref', 'condition-type?', 'cons', 'cons*', 'cons-source', 'continue', 'copy-bit', 'copy-bit-field', 'copy-file', 'copy-random-state', 'copy-time', 'copy-tree', 'cos', 'cosh', 'count', 'ctermid', 'current-date', 'current-dynamic-state', 'current-error-port', 'current-input-port', 'current-julian-day', 'current-load-port', 'current-modified-julian-day', 'current-module', 'current-output-port', 'current-time', 'cut', 'cute', 'date->julian-day', 'date->modified-julian-day', 'date->string', 'date->time-monotonic', 'date->time-tai', 'date->time-utc', 'date-day', 'date-hour', 'date-minute', 'date-month', 'date-nanosecond', 'date-second', 'date-week-day', 'date-week-number', 'date-year', 'date-year-day', 'date-zone-offset', 'date?', 'debug-object?', 'debug-options-interface', 'debug-trap', 'default-duplicate-binding-handler', 'define', 'define*', 'define*-public', 'define-condition-type', 'define-macro', 'define-module', 'define-public', 'define-reader-ctor', 'define-record-type', 'defined?', 'defmacro', 'defmacro*', 'defmacro*-public', 'delay', 'delete', 'delete!', 'delete-duplicates', 'delete-duplicates!', 'delete-file', 'delete1!', 'delq', 'delq!', 'delq1!', 'delv', 'delv!', 'delv1!', 'denominator', 'deq!', 'directory-stream?', 'dirname', 'display', 'display-application', 'display-backtrace', 'display-error', 'do', 'dotted-list?', 'doubly-weak-hash-table?', 'down', 'drain-input', 'drop', 'drop-right', 'drop-right!', 'drop-while', 'dup', 'dup->fdes', 'dup->inport', 'dup->outport', 'dup->port', 'dup2', 'duplicate-port', 'dynamic-args-call', 'dynamic-call', 'dynamic-func', 'dynamic-link', 'dynamic-object?', 'dynamic-state?', 'dynamic-unlink', 'dynamic-wind', 'effective-version', 'eighth', 'else', 'enclose-array', 'end-of-char-set?', 'enq!', 'entity?', 'environ', 'eof-object?', 'eq?', 'equal?', 'eqv?', 'error', 'error?', 'eval', 'eval-disable', 'eval-enable', 'eval-options', 'eval-options-interface', 'eval-set!', 'eval-string', 'evaluate', 'evaluator-traps-interface', 'even?', 'every', 'exact->inexact', 'exact?', 'execl', 'execle', 'execlp', 'exp', 'expect', 'expect-strings', 'export', 'expt', 'extract-condition', 'f32vector', 'f32vector->list', 'f32vector-length', 'f32vector-ref', 'f32vector-set!', 'f32vector?', 'f64vector', 'f64vector->list', 'f64vector-length', 'f64vector-ref', 'f64vector-set!', 'f64vector?', 'false-if-exception', 'fchmod', 'fchown', 'fcntl', 'fdes->inport', 'fdes->outport', 'fdes->ports', 'fdopen', 'feature?', 'fflush', 'fifth', 'file-exists?', 'file-port?', 'filename-completion-function', 'fileno', 'filter', 'filter!', 'filter-map', 'find', 'find-tail', 'first', 'first-set-bit', 'flock', 'floor', 'fluid-ref', 'fluid-set!', 'fluid?', 'flush-all-ports', 'fn', 'fold', 'fold-matches', 'fold-right', 'for-each', 'force', 'force-output', 'format', 'fourth', 'frame', 'frame-arguments', 'frame-evaluating-args?', 'frame-next', 'frame-number', 'frame-overflow?', 'frame-previous', 'frame-procedure', 'frame-procedure?', 'frame-real?', 'frame-source', 'frame?', 'fstat', 'fsync', 'ftell', 'ftruncate', 'ftw', 'gcd', 'generalized-vector->list', 'generalized-vector-length', 'generalized-vector-ref', 'generalized-vector-set!', 'generalized-vector?', 'gensym', 'get-internal-real-time', 'get-internal-run-time', 'get-output-string', 'get-print-state', 'getcwd', 'getegid', 'getenv', 'geteuid', 'getgid', 'getgroups', 'getpgrp', 'getpid', 'getppid', 'getpriority', 'getter-with-setter', 'gettimeofday', 'getuid', 'gmtime', 'hash', 'hash-by-identity', 'hash-clear!', 'hash-create-handle!', 'hash-fold', 'hash-for-each', 'hash-for-each-handle', 'hash-get-handle', 'hash-map->list', 'hash-ref', 'hash-remove!', 'hash-set!', 'hash-table->alist', 'hash-table-delete!', 'hash-table-equivalence-function', 'hash-table-exists?', 'hash-table-fold', 'hash-table-hash-function', 'hash-table-keys', 'hash-table-ref', 'hash-table-ref/default', 'hash-table-set!', 'hash-table-size', 'hash-table-update!', 'hash-table-update!/default', 'hash-table-values', 'hash-table-walk', 'hash-table?', 'hashq', 'hashq-create-handle!', 'hashq-get-handle', 'hashq-ref', 'hashq-remove!', 'hashq-set!', 'hashv', 'hashv-create-handle!', 'hashv-get-handle', 'hashv-ref', 'hashv-remove!', 'hashv-set!', 'hashx-create-handle!', 'hashx-get-handle', 'hashx-ref', 'hashx-remove!', 'hashx-set!', 'hook->list', 'hook-empty?', 'hook?', 'if', 'imag-part', 'inet-aton', 'inet-lnaof', 'inet-makeaddr', 'inet-netof', 'inet-ntoa', 'inet-ntop', 'inet-pton', 'inexact->exact', 'inexact?', 'inf', 'inf?', 'input-port?', 'install-trap', 'integer->char', 'integer->list', 'integer-expt', 'integer-length', 'integer?', 'interaction-environment', 'iota', 'isatty?', 'julian-day->date', 'julian-day->time-monotonic', 'julian-day->time-tai', 'julian-day->time-utc', 'key', 'keyword->string', 'keyword->symbol', 'keyword?', 'lambda', 'lambda*', 'last', 'last-pair', 'last-stack-frame', 'lazy-catch', 'lchown', 'lcm', 'length', 'length+', 'let', 'let*', 'let*-values', 'let-keywords', 'let-keywords*', 'let-optional', 'let-optional*', 'let-values', 'letpar', 'letrec', 'link', 'list', 'list->array', 'list->bitvector', 'list->c32vector', 'list->c64vector', 'list->char-set', 'list->char-set!', 'list->f32vector', 'list->f64vector', 'list->integer', 'list->s16vector', 'list->s32vector', 'list->s64vector', 'list->s8vector', 'list->stream', 'list->string', 'list->typed-array', 'list->u16vector', 'list->u32vector', 'list->u64vector', 'list->u8vector', 'list->vector', 'list->weak-vector', 'list-cdr-ref', 'list-cdr-set!', 'list-copy', 'list-head', 'list-index', 'list-matches', 'list-ref', 'list-set!', 'list-tabulate', 'list-tail', 'list=', 'list?', 'load', 'load-extension', 'load-from-path', 'local-eval', 'localtime', 'lock-mutex', 'log', 'log10', 'log2-binary-factors', 'logand', 'logbit?', 'logcount', 'logior', 'lognot', 'logtest', 'logxor', 'lset-adjoin', 'lset-diff+intersection', 'lset-diff+intersection!', 'lset-difference', 'lset-difference!', 'lset-intersection', 'lset-intersection!', 'lset-union', 'lset-union!', 'lset-xor', 'lset-xor!', 'lset<=', 'lset=', 'lstat', 'macro-name', 'macro-transformer', 'macro-type', 'macro?', 'magnitude', 'major-version', 'make-arbiter', 'make-array', 'make-bitvector', 'make-buffered-input-port', 'make-c32vector', 'make-c64vector', 'make-class-object', 'make-completion-function', 'make-compound-condition', 'make-condition', 'make-condition-type', 'make-condition-variable', 'make-date', 'make-doubly-weak-hash-table', 'make-dynamic-state', 'make-f32vector', 'make-f64vector', 'make-fluid', 'make-guardian', 'make-hash-table', 'make-hook', 'make-line-buffered-input-port', 'make-list', 'make-mutex', 'make-object-property', 'make-parameter', 'make-polar', 'make-procedure-with-setter', 'make-q', 'make-record-type', 'make-rectangular', 'make-recursive-mutex', 'make-regexp', 'make-s16vector', 'make-s32vector', 'make-s64vector', 'make-s8vector', 'make-shared-array', 'make-soft-port', 'make-stack', 'make-stream', 'make-string', 'make-struct', 'make-struct-layout', 'make-subclass-object', 'make-symbol', 'make-time', 'make-typed-array', 'make-u16vector', 'make-u32vector', 'make-u64vector', 'make-u8vector', 'make-undefined-variable', 'make-variable', 'make-vector', 'make-vtable', 'make-vtable-vtable', 'make-weak-key-hash-table', 'make-weak-value-hash-table', 'make-weak-vector', 'malloc-stats', 'map', 'map!', 'map-in-order', 'match:count', 'match:end', 'match:prefix', 'match:start', 'match:string', 'match:substring', 'match:suffix', 'max', 'member', 'memoized-environment', 'memoized?', 'memq', 'memv', 'merge', 'merge!', 'message-condition?', 'micro-version', 'min', 'minor-version', 'mkdir', 'mknod', 'mkstemp!', 'mktime', 'modified-julian-day->date', 'modified-julian-day->time-monotonic', 'modified-julian-day->time-tai', 'modified-julian-day->time-utc', 'module-use!', 'modulo', 'modulo-expt', 'monitor', 'move->fdes', 'n-for-each-par-map', 'n-par-for-each', 'n-par-map', 'nan', 'nan?', 'negative?', 'newline', 'nftw', 'nice', 'nil-car', 'nil-cdr', 'nil-cons', 'nil-eq', 'ninth', 'not', 'not-pair?', 'null', 'null-environment', 'null-list?', 'null?', 'number->string', 'number?', 'numerator', 'object->string', 'object-properties', 'object-property', 'odd?', 'open', 'open-fdes', 'open-file', 'open-input-file', 'open-input-output-pipe', 'open-input-pipe', 'open-input-string', 'open-output-file', 'open-output-pipe', 'open-output-string', 'open-pipe', 'open-pipe*', 'opendir', 'operator?', 'option-ref', 'or', 'output-port?', 'pair-fold', 'pair-fold-right', 'pair-for-each', 'pair?', 'par-for-each', 'par-map', 'parallel', 'parameterize', 'parse-path', 'partition', 'partition!', 'pclose', 'peek-char', 'pipe', 'popen', 'port->fdes', 'port->stream', 'port-closed?', 'port-column', 'port-filename', 'port-for-each', 'port-line', 'port-mode', 'port-revealed', 'port-with-print-state', 'port?', 'position', 'positive?', 'pretty-print', 'primitive-eval', 'primitive-exit', 'primitive-fork', 'primitive-load', 'primitive-load-path', 'primitive-make-property', 'primitive-move->fdes', 'primitive-property-del!', 'primitive-property-ref', 'primitive-property-set!', 'print-options-interface', 'procedure', 'procedure->macro', 'procedure->memoizing-macro', 'procedure->syntax', 'procedure-documentation', 'procedure-environment', 'procedure-name', 'procedure-properties', 'procedure-property', 'procedure-source', 'procedure-with-setter?', 'procedure?', 'program-arguments', 'promise?', 'proper-list?', 'provide', 'provided?', 'putenv', 'pwd', 'q-empty-check', 'q-empty?', 'q-front', 'q-length', 'q-pop!', 'q-push!', 'q-rear', 'q-remove!', 'q?', 'quasiquote', 'quit', 'quote', 'quotient', 'random', 'random:exp', 'random:hollow-sphere!', 'random:normal', 'random:normal-vector!', 'random:solid-sphere!', 'random:uniform', 'rational?', 'rationalize', 're-export', 'read', 'read-char', 'read-delimited', 'read-delimited!', 'read-disable', 'read-enable', 'read-hash-extend', 'read-line', 'read-line!', 'read-options', 'read-options-interface', 'read-set!', 'read-string!/partial', 'readdir', 'readline', 'readline-disable', 'readline-enable', 'readline-options', 'readline-port', 'readline-set!', 'readlink', 'real-part', 'real?', 'rec', 'receive', 'record-accessor', 'record-constructor', 'record-modifier', 'record-predicate', 'record-type-descriptor', 'record-type-fields', 'record-type-name', 'record?', 'redirect-port', 'reduce', 'reduce-right', 'regexp-exec', 'regexp-match?', 'regexp-quote', 'regexp-substitute', 'regexp-substitute/global', 'regexp?', 'release-arbiter', 'release-port-handle', 'remainder', 'remove', 'remove!', 'remove-hook!', 'rename', 'rename-file', 'require', 'require-extension', 'reset-hook!', 'resolve-interface', 'resolve-module', 'restricted-vector-sort!', 'reverse', 'reverse!', 'reverse-bit-field', 'reverse-list->string', 'rewinddir', 'rmdir', 'rotate-bit-field', 'round', 'run-hook', 's16vector', 's16vector->list', 's16vector-length', 's16vector-ref', 's16vector-set!', 's16vector?', 's32vector', 's32vector->list', 's32vector-length', 's32vector-ref', 's32vector-set!', 's32vector?', 's64vector', 's64vector->list', 's64vector-length', 's64vector-ref', 's64vector-set!', 's64vector?', 's8vector', 's8vector->list', 's8vector-length', 's8vector-ref', 's8vector-set!', 's8vector?', 'save-module-excursion', 'scheme-report-environment', 'scm-error', 'search-path', 'second', 'seed->random-state', 'seek', 'select', 'serious-condition?', 'set!', 'set-buffered-input-continuation?!', 'set-car!', 'set-cdr!', 'set-current-dynamic-state', 'set-current-error-port', 'set-current-input-port', 'set-current-module', 'set-current-output-port', 'set-object-procedure!', 'set-object-properties!', 'set-object-property!', 'set-port-column!', 'set-port-filename!', 'set-port-line!', 'set-port-revealed!', 'set-procedure-properties!', 'set-procedure-property!', 'set-program-arguments', 'set-readline-input-port!', 'set-readline-output-port!', 'set-readline-prompt!', 'set-source-properties!', 'set-source-property!', 'set-struct-vtable-name!', 'set-symbol-property!', 'set-time-nanosecond!', 'set-time-second!', 'set-time-type!', 'set-tm:gmtoff', 'set-tm:hour', 'set-tm:isdst', 'set-tm:mday', 'set-tm:min', 'set-tm:mon', 'set-tm:sec', 'set-tm:wday', 'set-tm:yday', 'set-tm:year', 'set-tm:zone', 'set-trace-layout', 'setegid', 'setenv', 'seteuid', 'setgid', 'setgroups', 'setlocale', 'setpgid', 'setpriority', 'setsid', 'setter', 'setuid', 'setvbuf', 'seventh', 'shared-array-increments', 'shared-array-offset', 'shared-array-root', 'signal-condition-variable', 'simple-format', 'sin', 'sinh', 'sixth', 'sloppy-assoc', 'sloppy-assq', 'sloppy-assv', 'sort', 'sort!', 'sort-list', 'sort-list!', 'sorted?', 'source-properties', 'source-property', 'span', 'span!', 'split-at', 'split-at!', 'sqrt', 'stable-sort', 'stable-sort!', 'stack-id', 'stack-length', 'stack-ref', 'stack?', 'start-stack', 'stat', 'stat:atime', 'stat:blksize', 'stat:blocks', 'stat:ctime', 'stat:dev', 'stat:gid', 'stat:ino', 'stat:mode', 'stat:mtime', 'stat:nlink', 'stat:perms', 'stat:rdev', 'stat:size', 'stat:type', 'stat:uid', 'status:exit-val', 'status:stop-sig', 'status:term-sig', 'stream->list', 'stream->list&length', 'stream->reversed-list', 'stream->reversed-list&length', 'stream->vector', 'stream-car', 'stream-cdr', 'stream-fold', 'stream-for-each', 'stream-map', 'stream-null?', 'strerror', 'strftime', 'string', 'string->char-set', 'string->char-set!', 'string->date', 'string->keyword', 'string->list', 'string->number', 'string->symbol', 'string-any', 'string-append', 'string-append/shared', 'string-capitalize', 'string-capitalize!', 'string-ci->symbol', 'string-ci-hash', 'string-ci<', 'string-ci<=', 'string-ci<=?', 'string-ci<>', 'string-ci', 'string-ci>=', 'string-ci>=?', 'string-ci>?', 'string-compare', 'string-compare-ci', 'string-concatenate', 'string-concatenate-reverse', 'string-concatenate-reverse/shared', 'string-concatenate/shared', 'string-contains', 'string-contains-ci', 'string-copy', 'string-copy!', 'string-count', 'string-delete', 'string-downcase', 'string-downcase!', 'string-drop', 'string-drop-right', 'string-every', 'string-fill!', 'string-filter', 'string-fold', 'string-fold-right', 'string-for-each', 'string-for-each-index', 'string-hash', 'string-hash-ci', 'string-index', 'string-index-right', 'string-join', 'string-length', 'string-map', 'string-map!', 'string-match', 'string-null?', 'string-pad', 'string-pad-right', 'string-prefix-ci?', 'string-prefix-length', 'string-prefix-length-ci', 'string-prefix?', 'string-ref', 'string-replace', 'string-reverse', 'string-reverse!', 'string-rindex', 'string-set!', 'string-skip', 'string-skip-right', 'string-split', 'string-suffix-ci?', 'string-suffix-length', 'string-suffix-length-ci', 'string-suffix?', 'string-tabulate', 'string-take', 'string-take-right', 'string-titlecase', 'string-titlecase!', 'string-tokenize', 'string-trim', 'string-trim-both', 'string-trim-right', 'string-unfold', 'string-unfold-right', 'string-upcase', 'string-upcase!', 'string-xcopy!', 'string<', 'string<=', 'string<=?', 'string<>', 'string', 'string>=', 'string>=?', 'string>?', 'string?', 'strptime', 'struct-ref', 'struct-set!', 'struct-vtable', 'struct-vtable-name', 'struct-vtable-tag', 'struct-vtable?', 'struct?', 'substring', 'substring-fill!', 'substring-move!', 'substring/copy', 'substring/read-only', 'substring/shared', 'subtract-duration', 'subtract-duration!', 'symbol->keyword', 'symbol->string', 'symbol-fref', 'symbol-fset!', 'symbol-hash', 'symbol-interned?', 'symbol-pref', 'symbol-prefix-proc', 'symbol-property', 'symbol-property-remove!', 'symbol-pset!', 'symbol?', 'symlink', 'sync', 'sync-q!', 'system', 'system*', 'system-error-errno', 'take', 'take!', 'take-right', 'take-while', 'take-while!', 'tan', 'tanh', 'tc:depth', 'tc:frame', 'tc:real-depth', 'tc:return-value', 'tc:stack', 'tc:type', 'tcgetpgrp', 'tcsetpgrp', 'tenth', 'third', 'throw', 'thunk?', 'time-difference', 'time-difference!', 'time-monotonic->date', 'time-monotonic->time-tai', 'time-monotonic->time-tai!', 'time-monotonic->time-utc', 'time-monotonic->time-utc!', 'time-nanosecond', 'time-resolution', 'time-second', 'time-tai->date', 'time-tai->julian-day', 'time-tai->modified-julian-day', 'time-tai->time-monotonic', 'time-tai->time-monotonic!', 'time-tai->time-utc', 'time-tai->time-utc!', 'time-type', 'time-utc->date', 'time-utc->julian-day', 'time-utc->modified-julian-day', 'time-utc->time-monotonic', 'time-utc->time-monotonic!', 'time-utc->time-tai', 'time-utc->time-tai!', 'time<=?', 'time=?', 'time>?', 'time?', 'times', 'tm:gmtoff', 'tm:hour', 'tm:isdst', 'tm:mday', 'tm:min', 'tm:mon', 'tm:sec', 'tm:wday', 'tm:yday', 'tm:year', 'tm:zone', 'tmpnam', 'tms:clock', 'tms:cstime', 'tms:cutime', 'tms:stime', 'tms:utime', 'trace', 'trace-at-exit', 'trace-port', 'trace-trap', 'trace-until-exit', 'trace/info', 'trace/pid', 'trace/real?', 'trace/source', 'trace/source-column', 'trace/source-file-name', 'trace/source-line', 'trace/stack', 'trace/stack-depth', 'trace/stack-id', 'trace/stack-real-depth', 'trace/type', 'transpose-array', 'trap-disable', 'trap-enable', 'trap-set!', 'traps', 'truncate', 'truncate-file', 'try-arbiter', 'try-mutex', 'ttyname', 'typed-array?', 'tzset', 'u16vector', 'u16vector->list', 'u16vector-length', 'u16vector-ref', 'u16vector-set!', 'u16vector?', 'u32vector', 'u32vector->list', 'u32vector-length', 'u32vector-ref', 'u32vector-set!', 'u32vector?', 'u64vector', 'u64vector->list', 'u64vector-length', 'u64vector-ref', 'u64vector-set!', 'u64vector?', 'u8vector', 'u8vector->list', 'u8vector-length', 'u8vector-ref', 'u8vector-set!', 'u8vector?', 'ucs-range->char-set', 'ucs-range->char-set!', 'umask', 'unfold', 'unfold-right', 'uniform-array-read!', 'uniform-array-write', 'uniform-vector->list', 'uniform-vector-length', 'uniform-vector-read!', 'uniform-vector-ref', 'uniform-vector-set!', 'uniform-vector-write', 'uniform-vector?', 'uninstall-trap', 'unlink', 'unlock-mutex', 'unmemoize', 'unquote', 'unquote-splicing', 'unread-char', 'unread-string', 'unsetenv', 'untrace', 'unzip1', 'unzip2', 'unzip3', 'unzip4', 'unzip5', 'up', 'use-modules', 'use-syntax', 'utime', 'values', 'variable-bound?', 'variable-ref', 'variable-set!', 'variable?', 'vector', 'vector->list', 'vector->stream', 'vector-copy', 'vector-fill!', 'vector-length', 'vector-move-left!', 'vector-move-right!', 'vector-ref', 'vector-set!', 'vector?', 'version', 'wait-condition-variable', 'waitpid', 'weak-key-hash-table?', 'weak-value-hash-table?', 'weak-vector', 'weak-vector?', 'while', 'with-continuation-barrier', 'with-dynamic-state', 'with-error-to-file', 'with-fluid*', 'with-fluids', 'with-fluids*', 'with-input-from-file', 'with-input-from-string', 'with-mutex', 'with-output-to-file', 'with-output-to-string', 'with-parameters*', 'with-readline-completion-function', 'with-throw-handler', 'with-traps', 'write', 'write-char', 'write-line', 'write-string/partial', 'xcons', 'xsubstring', 'zero?', 'zip', ] scheme_functions = [ 'accidental-interface::calc-alteration', 'accidental-interface::glyph-name', 'add-bar-glyph-print-procedure', 'add-grace-property', 'add-music', 'add-music-fonts', 'add-new-clef', 'add-pango-fonts', 'add-point', 'add-quotable', 'add-score', 'add-stroke-glyph', 'add-stroke-straight', 'add-text', 'adjust-slash-stencil', 'alist->hash-table', 'alisttext-accidental-markup', 'alterations-in-key', 'ambitus-line::calc-gap', 'ambitus::print', 'annotate-padding', 'annotate-spacing-spec', 'annotate-y-interval', 'arrow-stencil-maker', 'attributes', 'average', 'backend-testing', 'banter-chord-names', 'bar-line::calc-break-visibility', 'bar-line::calc-glyph-name', 'bar-line::compound-bar-line', 'bar-line::widen-bar-extent-on-span', 'base-length', 'beam-exceptions', 'beam::align-with-broken-parts', 'beam::get-kievan-positions', 'beam::get-kievan-quantized-positions', 'beam::place-broken-parts-individually', 'beam::slope-like-broken-parts', 'beat-structure', 'bend::print', 'binary-search', 'boolean-or-symbol?', 'box-grob-stencil', 'box-stencil', 'bracketify-stencil', 'cached-file-contents', 'calc-harmonic-pitch', 'calculate-compound-base-beat', 'calculate-compound-beat-grouping', 'calculate-compound-measure-length', 'call-after-session', 'car<', 'centered-stencil', 'chain-grob-member-functions', 'change-pitches', 'cheap-list?', 'check-grob-path', 'check-quant-callbacks', 'check-slope-callbacks', 'circle-stencil', 'clef-transposition-markup', 'clef::print-modern-tab-if-set', 'clip-system-EPSes', 'collect-book-music-for-book', 'collect-bookpart-for-book', 'collect-music-aux', 'collect-music-for-book', 'collect-scores-for-book', 'color?', 'comment', 'completize-formats', 'compose', 'construct-chord-elements', 'context-defs-from-music', 'context-mod-from-music', 'context-spec-music', 'convert-to-pdf', 'convert-to-png', 'convert-to-ps', 'coord-rotate', 'coord-scale', 'coord-translate', 'copy-repeat-chord', 'count-list', 'coverage:disable', 'coverage:enable', 'coverage:show-all', 'create-glyph-flag', 'cross-staff-connect', 'cue-substitute', 'cyclic-base-value', 'debugf', 'decode-byte-string', 'def-grace-function', 'default-auto-beam-check', 'default-dynamic-absolute-volume', 'default-flag', 'default-instrument-equalizer', 'define-bar-line', 'define-event-class', 'define-event-function', 'define-fonts', 'define-markup-command', 'define-markup-list-command', 'define-music-function', 'define-scheme-function', 'define-session', 'define-session-public', 'define-syntax-function', 'define-void-function', 'degrees->radians', 'descend-to-context', 'determine-frets', 'determine-split-list', 'dir-basename', 'display-lily-music', 'display-music', 'display-scheme-music', 'dots::calc-dot-count', 'dots::calc-staff-position', 'dump-gc-protects', 'dump-live-object-stats', 'dump-stencil-as-EPS', 'dump-stencil-as-EPS-with-bbox', 'duration->lily-string', 'duration-dot-factor', 'duration-length', 'duration-log-factor', 'duration-of-note', 'duration-visual', 'duration-visual-length', 'dynamic-text-spanner::before-line-breaking', 'ec', 'ellipse-radius', 'ellipse-stencil', 'empty-music', 'entity', 'eo', 'eoc', 'eps-file->stencil', 'ergonomic-simple-format', 'eval-carefully', 'event-cause', 'event-chord-notes', 'event-chord-pitches', 'event-chord-reduce', 'event-class-cons', 'extract-music', 'extract-named-music', 'extract-typed-music', 'find-child', 'find-child-named', 'find-pitch-entry', 'fingering::calc-text', 'first-assoc', 'first-bar-number-invisible', 'first-bar-number-invisible-and-no-parenthesized-bar-numbers', 'first-bar-number-invisible-save-broken-bars', 'first-member', 'flat-flag', 'flatten-alist', 'flatten-list', 'fold-some-music', 'font-name-split', 'font-name-style', 'for-some-music', 'format-bass-figure', 'format-compound-time', 'format-mark-alphabet', 'format-mark-barnumbers', 'format-mark-box-alphabet', 'format-mark-box-barnumbers', 'format-mark-box-letters', 'format-mark-box-numbers', 'format-mark-circle-alphabet', 'format-mark-circle-barnumbers', 'format-mark-circle-letters', 'format-mark-circle-numbers', 'format-mark-letters', 'format-mark-numbers', 'format-metronome-markup', 'fraction->moment', 'fraction?', 'fret->pitch', 'fret-board::calc-stencil', 'fret-letter-tablature-format', 'fret-number-tablature-format', 'fret-number-tablature-format-banjo', 'fret-parse-terse-definition-string', 'function-chain', 'get-chord-shape', 'get-editor-command', 'get-woodwind-key-list', 'glissando::calc-tab-extra-dy', 'glissando::draw-tab-glissando', 'grace-spacing::calc-shortest-duration', 'grob-interpret-markup', 'grob-list?', 'grob::has-interface', 'grob::is-live?', 'grob::unpure-Y-extent-from-stencil', 'gui-main', 'gulp-file', 'hairpin::calc-grow-direction', 'hash-table->alist', 'horizontal-slash-interval', 'identifiers-doc-string', 'ignatzek-chord-names', 'index?', 'internal-add-text-replacements', 'interpret-markup-list', 'interval-bound', 'interval-center', 'interval-empty?', 'interval-index', 'interval-intersection', 'interval-length', 'interval-sane?', 'interval-scale', 'interval-union', 'interval-widen', 'invalidate-alterations', 'is-absolute?', 'jazz-chord-names', 'key-signature-interface::alteration-position', 'key-signature-interface::alteration-positions', 'laissez-vibrer::print', 'layout-extract-page-properties', 'layout-line-thickness', 'layout-set-absolute-staff-size', 'layout-set-absolute-staff-size-in-module', 'layout-set-staff-size', 'lilypond-all', 'lilypond-main', 'lilypond-version', 'list-insert-separator', 'list-join', 'lookup-markup-command', 'lookup-markup-list-command', 'ly-getcwd', 'ly:accidental-interface::height', 'ly:accidental-interface::horizontal-skylines', 'ly:accidental-interface::print', 'ly:accidental-interface::pure-height', 'ly:accidental-interface::width', 'ly:accidental-placement::calc-positioning-done', 'ly:add-context-mod', 'ly:add-file-name-alist', 'ly:add-interface', 'ly:add-listener', 'ly:add-option', 'ly:align-interface::align-to-ideal-distances', 'ly:align-interface::align-to-minimum-distances', 'ly:alist-cilisp-identifier', 'ly:chain-assoc-get', 'ly:change-iterator::constructor', 'ly:char-cistring', 'ly:duration-dot-count', 'ly:duration-factor', 'ly:duration-length', 'ly:duration-log', 'ly:duration-scale', 'ly:durationlist', 'ly:grob-array-length', 'ly:grob-array-ref', 'ly:grob-array?', 'ly:grob-basic-properties', 'ly:grob-chain-callback', 'ly:grob-common-refpoint', 'ly:grob-common-refpoint-of-array', 'ly:grob-default-font', 'ly:grob-extent', 'ly:grob-get-vertical-axis-group-index', 'ly:grob-interfaces', 'ly:grob-layout', 'ly:grob-object', 'ly:grob-original', 'ly:grob-parent', 'ly:grob-pqstring', 'ly:input-both-locations', 'ly:input-file-line-char-column', 'ly:input-location?', 'ly:input-message', 'ly:input-warning', 'ly:interpret-music-expression', 'ly:interpret-stencil-expression', 'ly:intlog2', 'ly:item-break-dir', 'ly:item?', 'ly:iterator?', 'ly:key-signature-interface::print', 'ly:kievan-ligature::print', 'ly:ledger-line-spanner::print', 'ly:ledger-line-spanner::set-spacing-rods', 'ly:lexer-keywords', 'ly:lily-lexer?', 'ly:lily-parser?', 'ly:line-spanner::calc-cross-staff', 'ly:line-spanner::calc-left-bound-info', 'ly:line-spanner::calc-left-bound-info-and-text', 'ly:line-spanner::calc-right-bound-info', 'ly:line-spanner::print', 'ly:list->offsets', 'ly:listened-event-class?', 'ly:listened-event-types', 'ly:listener?', 'ly:load', 'ly:lyric-combine-music-iterator::constructor', 'ly:lyric-extender::print', 'ly:lyric-hyphen::print', 'ly:lyric-hyphen::set-spacing-rods', 'ly:make-book', 'ly:make-book-part', 'ly:make-context-mod', 'ly:make-dispatcher', 'ly:make-duration', 'ly:make-event-class', 'ly:make-global-context', 'ly:make-global-translator', 'ly:make-listener', 'ly:make-moment', 'ly:make-music', 'ly:make-music-function', 'ly:make-music-relative!', 'ly:make-output-def', 'ly:make-page-label-marker', 'ly:make-page-permission-marker', 'ly:make-pango-description-string', 'ly:make-paper-outputter', 'ly:make-pitch', 'ly:make-prob', 'ly:make-scale', 'ly:make-score', 'ly:make-simple-closure', 'ly:make-spring', 'ly:make-stencil', 'ly:make-stream-event', 'ly:make-undead', 'ly:make-unpure-pure-container', 'ly:measure-grouping::print', 'ly:mensural-ligature::brew-ligature-primitive', 'ly:mensural-ligature::print', 'ly:message', 'ly:minimal-breaking', 'ly:mm', 'ly:module->alist', 'ly:module-copy', 'ly:modules-lookup', 'ly:moment->string', 'ly:moment-add', 'ly:moment-div', 'ly:moment-grace', 'ly:moment-grace-denominator', 'ly:moment-grace-numerator', 'ly:moment-main', 'ly:moment-main-denominator', 'ly:moment-main-numerator', 'ly:moment-mod', 'ly:moment-mul', 'ly:moment-sub', 'ly:momentstring', 'ly:number-pair->string', 'ly:one-line-breaking', 'ly:optimal-breaking', 'ly:option-usage', 'ly:otf->cff', 'ly:otf-font-glyph-info', 'ly:otf-font-table-data', 'ly:otf-font?', 'ly:otf-glyph-count', 'ly:otf-glyph-list', 'ly:ottava-bracket::print', 'ly:output-def-clone', 'ly:output-def-lookup', 'ly:output-def-parent', 'ly:output-def-scope', 'ly:output-def-set-variable!', 'ly:output-def?', 'ly:output-description', 'ly:output-find-context-def', 'ly:output-formats', 'ly:outputter-close', 'ly:outputter-dump-stencil', 'ly:outputter-dump-string', 'ly:outputter-module', 'ly:outputter-output-scheme', 'ly:outputter-port', 'ly:page-marker?', 'ly:page-turn-breaking', 'ly:pango-font-physical-fonts', 'ly:pango-font?', 'ly:paper-book-header', 'ly:paper-book-pages', 'ly:paper-book-paper', 'ly:paper-book-performances', 'ly:paper-book-scopes', 'ly:paper-book-systems', 'ly:paper-book?', 'ly:paper-column::before-line-breaking', 'ly:paper-column::print', 'ly:paper-fonts', 'ly:paper-get-font', 'ly:paper-get-number', 'ly:paper-outputscale', 'ly:paper-score-paper-systems', 'ly:paper-system-minimum-distance', 'ly:paper-system?', 'ly:parse-file', 'ly:parse-string-expression', 'ly:parsed-undead-list!', 'ly:parser-clear-error', 'ly:parser-clone', 'ly:parser-define!', 'ly:parser-error', 'ly:parser-has-error?', 'ly:parser-include-string', 'ly:parser-lexer', 'ly:parser-lookup', 'ly:parser-output-name', 'ly:parser-parse-string', 'ly:parser-set-note-names', 'ly:part-combine-iterator::constructor', 'ly:partial-iterator::constructor', 'ly:percent-repeat-item-interface::beat-slash', 'ly:percent-repeat-item-interface::double-percent', 'ly:percent-repeat-iterator::constructor', 'ly:performance-write', 'ly:pfb->pfa', 'ly:piano-pedal-bracket::print', 'ly:pitch-alteration', 'ly:pitch-diff', 'ly:pitch-negate', 'ly:pitch-notename', 'ly:pitch-octave', 'ly:pitch-quartertones', 'ly:pitch-semitones', 'ly:pitch-steps', 'ly:pitch-tones', 'ly:pitch-transpose', 'ly:pitchpfa', 'ly:ttf-ps-name', 'ly:tuplet-bracket::calc-connect-to-neighbors', 'ly:tuplet-bracket::calc-cross-staff', 'ly:tuplet-bracket::calc-direction', 'ly:tuplet-bracket::calc-positions', 'ly:tuplet-bracket::calc-x-positions', 'ly:tuplet-bracket::print', 'ly:tuplet-iterator::constructor', 'ly:tuplet-number::calc-cross-staff', 'ly:tuplet-number::calc-x-offset', 'ly:tuplet-number::calc-y-offset', 'ly:tuplet-number::print', 'ly:undead?', 'ly:unfolded-repeat-iterator::constructor', 'ly:unit', 'ly:unpure-pure-container-pure-part', 'ly:unpure-pure-container-unpure-part', 'ly:unpure-pure-container?', 'ly:usage', 'ly:vaticana-ligature::brew-ligature-primitive', 'ly:vaticana-ligature::print', 'ly:verbose-output?', 'ly:version', 'ly:volta-bracket-interface::print', 'ly:volta-bracket::calc-shorten-pair', 'ly:volta-repeat-iterator::constructor', 'ly:warning', 'ly:warning-located', 'ly:wide-char->utf-8', 'lyric-text::print', 'magnification->font-size', 'magstep', 'make-apply-context', 'make-articulation', 'make-autochange-music', 'make-century-schoolbook-tree', 'make-circle-stencil', 'make-clef-set', 'make-connected-path-stencil', 'make-cue-clef-set', 'make-cue-clef-unset', 'make-duration-of-length', 'make-ellipse-stencil', 'make-engraver', 'make-event-chord', 'make-filled-box-stencil', 'make-grace-music', 'make-graceless-rhythmic-location', 'make-grob-property-override', 'make-grob-property-revert', 'make-grob-property-set', 'make-harmonic', 'make-line-stencil', 'make-lyric-event', 'make-markup', 'make-modal-inverter', 'make-modal-transposer', 'make-multi-measure-rest', 'make-music', 'make-non-relative-music', 'make-oval-stencil', 'make-pango-font-tree', 'make-part-combine-music', 'make-partial-ellipse-stencil', 'make-property-set', 'make-property-unset', 'make-ps-images', 'make-relative', 'make-repeat', 'make-repeated-music', 'make-rhythmic-location', 'make-safe-lilypond-module', 'make-sequential-music', 'make-setting', 'make-simultaneous-music', 'make-skip-music', 'make-span-event', 'make-stencil-boxer', 'make-stencil-circler', 'make-stream-event', 'make-transparent-box-stencil', 'make-type-checker', 'make-voice-props-override', 'make-voice-props-revert', 'make-voice-props-set', 'map-selected-alist-keys', 'map-some-music', 'markup', 'markup->lily-string', 'markup->string', 'markup-command-list?', 'markup-command-signature-ref', 'markup-list?', 'memoize-clef-names', 'mensural-flag', 'metronome-markup', 'midi-program', 'mmrest-of-length', 'modern-straight-flag', 'modified-font-metric-font-scaling', 'moment->fraction', 'moment-min', 'moment-pair?', 'mtrace:dump-results', 'mtrace:start-trace', 'mtrace:stop-trace', 'music->lily-string', 'music->make-music', 'music-clone', 'music-elements', 'music-filter', 'music-has-property?', 'music-has-type', 'music-invert', 'music-is-of-type?', 'music-map', 'music-name?', 'music-property-value?', 'music-property?', 'music-separator?', 'music-to-musicxml', 'music-to-xml', 'myd', 'neo-modern-accidental-rule', 'no-flag', 'normal-flag', 'note-head::brew-ez-stencil', 'note-head::calc-duration-log', 'note-head::calc-glyph-name', 'note-head::calc-kievan-duration-log', 'note-name->german-markup', 'note-name->lily-string', 'note-name->markup', 'note-names-language', 'note-to-cluster', 'notes-to-clusters', 'number-list?', 'number-or-grob?', 'number-or-markup?', 'number-or-pair?', 'number-or-string?', 'number-pair-list?', 'number-pair?', 'numbered-footnotes', 'object-type', 'object-type-name', 'octave->lily-string', 'offset-add', 'offset-flip-y', 'offset-fret', 'offset-scale', 'offsetter', 'old-straight-flag', 'only-if-beamed', 'ordered-cons', 'output-classic-framework', 'output-file', 'output-framework', 'output-preview-framework', 'output-scopes', 'oval-stencil', 'override-head-style', 'override-time-signature-setting', 'page-stencil', 'pango-pf-file-name', 'pango-pf-font-name', 'pango-pf-fontindex', 'paper-system-annotate', 'paper-system-annotate-last', 'paper-system-extent', 'paper-system-layout', 'paper-system-staff-extents', 'paper-system-stencil', 'paper-system-system-grob', 'paper-system-title?', 'parentheses-item::calc-angled-bracket-stencils', 'parentheses-item::calc-parenthesis-stencils', 'parentheses-item::print', 'parenthesize-stencil', 'parse-terse-string', 'percussion?', 'pitch-invert', 'pitch-of-note', 'polar->rectangular', 'postprocess-output', 'postscript->pdf', 'postscript->png', 'prepend-alist-chain', 'print', 'print-book-with-defaults', 'print-book-with-defaults-as-systems', 'print-circled-text-callback', 'print-keys', 'print-keys-verbose', 'process-music', 'property-value', 'ps-embed-cff', 'ps-font-command', 'ps-page-count', 'pure-chain-offset-callback', 'pure-from-neighbor-interface::account-for-span-bar', 'pure-from-neighbor-interface::extra-spacing-height', 'pure-from-neighbor-interface::extra-spacing-height-at-beginning-of-line', 'pure-from-neighbor-interface::extra-spacing-height-including-staff', 'pure-from-neighbor-interface::pure-height', 'ratio->fret', 'ratio->pitch', 'read-lily-expression', 'recording-group-emulate', 'relevant-book-systems', 'relevant-dump-systems', 'remove-grace-property', 'remove-stencil-warnings', 'repeat-tie::handle-tab-note-head', 'retrieve-glyph-flag', 'retrograde-music', 'reverse-interval', 'revert-head-style', 'revert-time-signature-setting', 'rgb-color', 'rhythmic-location->file-string', 'rhythmic-location->string', 'rhythmic-location?', 'rhythmic-location?', 'robust-bar-number-function', 'rounded-box-stencil', 'safe-car', 'safe-last', 'sanitize-command-option', 'scale-by-font-size', 'scale-layout', 'scheme?', 'scm->string', 'scorify-music', 'script-interface::calc-x-offset', 'script-or-side-position-cross-staff', 'search-executable', 'search-gs', 'select-head-glyph', 'semi-tie::calc-cross-staff', 'sequential-music-to-chord-exceptions', 'session-initialize', 'set-accidental-style', 'set-accidentals-properties', 'set-default-paper-size', 'set-global-staff-size', 'set-paper-dimension-variables', 'set-paper-size', 'shift-duration-log', 'shift-one-duration-log', 'shift-right-at-line-begin', 'skip->rest', 'skip-of-length', 'skyline-pair-and-non-empty?', 'skyline-pair::empty?', 'slur::draw-tab-slur', 'space-lines', 'span-bar::compound-bar-line', 'span-bar::notify-grobs-of-my-existence', 'split-list-by-separator', 'stack-lines', 'stack-stencil-line', 'stack-stencils', 'stack-stencils-padding-list', 'stderr', 'stem-stub::extra-spacing-height', 'stem-stub::pure-height', 'stem-stub::width', 'stem-tremolo::calc-tab-width', 'stem::calc-duration-log', 'stem::kievan-offset-callback', 'stencil-whiteout', 'stencil-with-color', 'straight-flag', 'string-encode-integer', 'string-endswith', 'string-number::calc-text', 'string-or-music?', 'string-or-pair?', 'string-or-symbol?', 'string-regexp-substitute', 'string-startswith', 'stroke-finger::calc-text', 'style-note-heads', 'symbol-concatenate', 'symbol-footnotes', 'symbol-keymarkup', 'unfold-repeats', 'unfold-repeats-fully', 'uniq-list', 'uniqued-alist', 'value->lily-string', 'vector-for-each', 'version-not-seen-message', 'voicify-music', 'void?', 'volta-bracket-interface::pure-height', 'volta-bracket::calc-hook-visibility', 'warning', 'write-me', 'write-performances-midis', 'write-system-signature', 'write-system-signatures', ] scheme_variables = [ 'accidental-interface::height', 'all-backend-properties', 'all-internal-grob-properties', 'all-internal-translation-properties', 'all-invisible', 'all-music-font-encodings', 'all-music-properties', 'all-text-font-encodings', 'all-translation-properties', 'all-user-grob-properties', 'all-user-translation-properties', 'all-visible', 'alteration-default-glyph-name-alist', 'alteration-hufnagel-glyph-name-alist', 'alteration-kievan-glyph-name-alist', 'alteration-medicaea-glyph-name-alist', 'alteration-mensural-glyph-name-alist', 'alteration-vaticana-glyph-name-alist', 'arglist', 'assoc-get', 'axis-group-interface::height', 'begin-of-line-invisible', 'begin-of-line-visible', 'black', 'blue', 'cancellation-glyph-name-alist', 'center-invisible', 'center-visible', 'chain-assoc-get', 'constante-hairpin', 'current-outfile-name', 'cyan', 'darkblue', 'darkcyan', 'darkgreen', 'darkmagenta', 'darkred', 'darkyellow', 'default-chord-modifier-list', 'default-language', 'default-melisma-properties', 'default-script-alist', 'default-string-replacement-alist', 'default-time-signature-settings', 'dimension-arrows', 'dynamic-default-volume', 'empty-interval', 'empty-markup', 'empty-stencil', 'end-of-line-invisible', 'end-of-line-visible', 'fancy-format', 'feta-design-size-mapping', 'filtered-map', 'flared-hairpin', 'green', 'grey', 'grob::always-Y-extent-from-stencil', 'grob::always-horizontal-skylines-from-element-stencils', 'grob::always-horizontal-skylines-from-stencil', 'grob::always-vertical-skylines-from-element-stencils', 'grob::always-vertical-skylines-from-stencil', 'grob::unpure-horizontal-skylines-from-stencil', 'grob::unpure-vertical-skylines-from-stencil', 'guile-predicates', 'interpret-markup', 'interval-end', 'interval-start', 'language-pitch-names', 'lily-unit->bigpoint-factor', 'lily-unit->mm-factor', 'lilypond-exported-predicates', 'lilypond-scheme-predicates', 'magenta', 'makam-alteration-glyph-name-alist', 'markup-command-signature', 'markup-functions-by-category', 'markup-functions-properties', 'markup-list-functions', 'markup?', 'mtrace:trace-depth', 'music-descriptions', 'music-name-to-property-table', 'paper-alist', 'paper-variable', 'pitchnames', 'point-stencil', 'previous-pitchnames', 'pure-from-neighbor-interface::height-if-pure', 'red', 'rhythmic-location-bar-number', 'rhythmic-location-measure-position', 'score-grace-settings', 'self-alignment-interface::y-aligned-on-self', 'side-position-interface::y-aligned-side', 'slur::height', 'standard-alteration-glyph-name-alist', 'supported-clefs', 'type-predicates-doc-string', 'white', 'woodwind-instrument-list', 'yellow', ] scheme_constants = [ 'CENTER', 'DOS', 'DOUBLE-FLAT', 'DOUBLE-FLAT-QTS', 'DOUBLE-SHARP', 'DOUBLE-SHARP-QTS', 'DOWN', 'FLAT', 'FLAT-QTS', 'INFINITY-INT', 'LEFT', 'NATURAL', 'NATURAL-QTS', 'PI', 'PI-OVER-TWO', 'PLATFORM', 'RIGHT', 'SEMI-FLAT', 'SEMI-FLAT-QTS', 'SEMI-SHARP', 'SEMI-SHARP-QTS', 'SEMI-TONE', 'SEMI-TONE-QTS', 'SHARP', 'SHARP-QTS', 'START', 'STOP', 'THREE-PI-OVER-TWO', 'THREE-Q-FLAT', 'THREE-Q-FLAT-QTS', 'THREE-Q-SHARP', 'THREE-Q-SHARP-QTS', 'TWO-PI', 'UP', 'X', 'Y', 'ZERO-MOMENT', ] python-ly-0.9.3/ly/data/_lilypond_data.py0000644000175000017500000020136012501542536021526 0ustar wilbertwilbert00000000000000# generated by LilyPond 2.18.2 version = "2.18.2" interfaces = { "accidental-interface": [ "alteration", "avoid-slur", "glyph-name", "glyph-name-alist", "hide-tied-accidental-after-break", "parenthesized", "restore-first", ], "accidental-placement-interface": [ "direction", "padding", "positioning-done", "right-padding", "script-priority", ], "accidental-suggestion-interface": [], "align-interface": [ "align-dir", "axes", "padding", "positioning-done", "stacking-dir", ], "ambitus-interface": [ "gap", "length-fraction", "maximum-gap", "thickness", ], "arpeggio-interface": [ "arpeggio-direction", "dash-definition", "positions", "protrusion", "script-priority", ], "axis-group-interface": [ "axes", "default-staff-staff-spacing", "max-stretch", "no-alignment", "nonstaff-nonstaff-spacing", "nonstaff-relatedstaff-spacing", "nonstaff-unrelatedstaff-spacing", "outside-staff-placement-directive", "staff-affinity", "staff-staff-spacing", ], "balloon-interface": [ "annotation-balloon", "annotation-line", "padding", "text", ], "bar-line-interface": [ "allow-span-bar", "bar-extent", "gap", "glyph", "glyph-name", "hair-thickness", "kern", "rounded", "thick-thickness", "thin-kern", ], "bass-figure-alignment-interface": [], "bass-figure-interface": [ "implicit", ], "beam-interface": [ "X-positions", "annotation", "auto-knee-gap", "beam-thickness", "beamed-stem-shorten", "beaming", "break-overshoot", "clip-edges", "collision-interfaces", "collision-voice-only", "concaveness", "damping", "details", "direction", "gap", "gap-count", "grow-direction", "inspect-quants", "knee", "length-fraction", "neutral-direction", "positions", "skip-quanting", ], "bend-after-interface": [ "thickness", ], "break-alignable-interface": [ "break-align-symbols", "non-break-align-symbols", ], "break-aligned-interface": [ "break-align-anchor", "break-align-anchor-alignment", "break-align-symbol", "space-alist", ], "break-alignment-interface": [ "break-align-orders", "positioning-done", ], "breathing-sign-interface": [ "direction", ], "chord-name-interface": [], "clef-interface": [ "full-size-change", "glyph", "glyph-name", "non-default", ], "clef-modifier-interface": [], "cluster-beacon-interface": [ "positions", ], "cluster-interface": [ "padding", "style", ], "custos-interface": [ "neutral-direction", "neutral-position", "style", ], "dot-column-interface": [ "direction", "positioning-done", ], "dots-interface": [ "direction", "dot-count", "style", ], "dynamic-interface": [], "dynamic-line-spanner-interface": [ "avoid-slur", ], "dynamic-text-interface": [ "right-padding", ], "dynamic-text-spanner-interface": [ "text", ], "enclosing-bracket-interface": [ "bracket-flare", "edge-height", "padding", "shorten-pair", "thickness", ], "episema-interface": [], "figured-bass-continuation-interface": [ "padding", "thickness", ], "finger-interface": [], "fingering-column-interface": [ "padding", "positioning-done", "snap-radius", ], "flag-interface": [ "glyph-name", "stroke-style", "style", ], "font-interface": [ "font-encoding", "font-family", "font-name", "font-series", "font-shape", "font-size", ], "footnote-interface": [ "automatically-numbered", "footnote", "footnote-text", ], "footnote-spanner-interface": [ "footnote-text", ], "fret-diagram-interface": [ "align-dir", "dot-placement-list", "fret-diagram-details", "size", "thickness", ], "glissando-interface": [], "grace-spacing-interface": [ "common-shortest-duration", ], "gregorian-ligature-interface": [], "grid-line-interface": [ "thickness", ], "grid-point-interface": [], "grob-interface": [ "X-extent", "X-offset", "Y-extent", "Y-offset", "after-line-breaking", "avoid-slur", "before-line-breaking", "color", "cross-staff", "extra-offset", "footnote-music", "forced-spacing", "horizontal-skylines", "id", "layer", "minimum-X-extent", "minimum-Y-extent", "outside-staff-horizontal-padding", "outside-staff-padding", "outside-staff-priority", "rotation", "skyline-horizontal-padding", "springs-and-rods", "stencil", "transparent", "vertical-skylines", "whiteout", ], "hairpin-interface": [ "bound-padding", "broken-bound-padding", "circled-tip", "grow-direction", "height", ], "hara-kiri-group-spanner-interface": [ "remove-empty", "remove-first", ], "horizontal-bracket-interface": [ "bracket-flare", "connect-to-neighbor", "edge-height", "shorten-pair", ], "inline-accidental-interface": [], "instrument-specific-markup-interface": [ "fret-diagram-details", "graphical", "harp-pedal-details", "size", "thickness", ], "item-interface": [ "break-visibility", "extra-spacing-height", "extra-spacing-width", "non-musical", ], "key-cancellation-interface": [], "key-signature-interface": [ "alteration-alist", "flat-positions", "glyph-name-alist", "padding", "padding-pairs", "sharp-positions", ], "kievan-ligature-interface": [ "padding", ], "ledger-line-spanner-interface": [ "gap", "length-fraction", "minimum-length-fraction", ], "ledgered-interface": [ "no-ledgers", ], "ligature-bracket-interface": [ "height", "thickness", "width", ], "ligature-head-interface": [], "ligature-interface": [], "line-interface": [ "arrow-length", "arrow-width", "dash-fraction", "dash-period", "style", "thickness", "zigzag-length", "zigzag-width", ], "line-spanner-interface": [ "bound-details", "extra-dy", "gap", "left-bound-info", "right-bound-info", "simple-Y", "thickness", "to-barline", ], "lyric-extender-interface": [ "left-padding", "next", "right-padding", "thickness", ], "lyric-hyphen-interface": [ "dash-period", "height", "length", "minimum-distance", "minimum-length", "padding", "thickness", ], "lyric-interface": [], "lyric-syllable-interface": [], "mark-interface": [], "measure-counter-interface": [ "count-from", ], "measure-grouping-interface": [ "height", "style", "thickness", ], "melody-spanner-interface": [ "neutral-direction", ], "mensural-ligature-interface": [ "thickness", ], "metronome-mark-interface": [], "multi-measure-interface": [ "bound-padding", ], "multi-measure-rest-interface": [ "bound-padding", "expand-limit", "hair-thickness", "measure-count", "minimum-length", "round-up-exceptions", "round-up-to-longer-rest", "spacing-pair", "thick-thickness", "usable-duration-logs", ], "note-collision-interface": [ "merge-differently-dotted", "merge-differently-headed", "positioning-done", "prefer-dotted-right", ], "note-column-interface": [ "force-hshift", "horizontal-shift", "ignore-collision", ], "note-head-interface": [ "glyph-name", "note-names", "stem-attachment", "style", ], "note-name-interface": [], "note-spacing-interface": [ "knee-spacing-correction", "same-direction-correction", "space-to-barline", "stem-spacing-correction", ], "only-prebreak-interface": [], "ottava-bracket-interface": [ "bracket-flare", "edge-height", "minimum-length", "shorten-pair", ], "paper-column-interface": [ "between-cols", "full-measure-extra-space", "labels", "line-break-penalty", "line-break-permission", "line-break-system-details", "page-break-penalty", "page-break-permission", "page-turn-penalty", "page-turn-permission", "rhythmic-location", "shortest-playing-duration", "shortest-starter-duration", "used", "when", ], "parentheses-interface": [ "padding", "stencils", ], "percent-repeat-interface": [ "dot-negative-kern", "slash-negative-kern", "slope", "thickness", ], "percent-repeat-item-interface": [ "dot-negative-kern", "slash-negative-kern", "slope", "thickness", ], "piano-pedal-bracket-interface": [ "bound-padding", "bracket-flare", "edge-height", "shorten-pair", ], "piano-pedal-interface": [], "piano-pedal-script-interface": [], "pitched-trill-interface": [], "pure-from-neighbor-interface": [], "rest-collision-interface": [ "minimum-distance", "positioning-done", ], "rest-interface": [ "direction", "minimum-distance", "style", ], "rhythmic-grob-interface": [], "rhythmic-head-interface": [ "duration-log", "glissando-skip", ], "script-column-interface": [], "script-interface": [ "avoid-slur", "positioning-done", "script-priority", "side-relative-direction", "slur-padding", "toward-stem-shift", ], "self-alignment-interface": [ "collision-bias", "collision-padding", "self-alignment-X", "self-alignment-Y", ], "semi-tie-column-interface": [ "direction", "head-direction", "positioning-done", "tie-configuration", ], "semi-tie-interface": [ "control-points", "details", "direction", "head-direction", "thickness", ], "separation-item-interface": [ "X-extent", "horizontal-skylines", "padding", "skyline-vertical-padding", ], "side-position-interface": [ "add-stem-support", "direction", "horizon-padding", "minimum-space", "padding", "side-axis", "slur-padding", "staff-padding", "use-skylines", ], "slur-interface": [ "annotation", "avoid-slur", "control-points", "dash-definition", "details", "direction", "eccentricity", "height-limit", "inspect-index", "inspect-quants", "line-thickness", "positions", "ratio", "thickness", ], "spaceable-grob-interface": [ "allow-loose-spacing", "keep-inside-line", "measure-length", ], "spacing-interface": [], "spacing-options-interface": [ "shortest-duration-space", "spacing-increment", ], "spacing-spanner-interface": [ "average-spacing-wishes", "base-shortest-duration", "common-shortest-duration", "packed-spacing", "shortest-duration-space", "spacing-increment", "strict-grace-spacing", "strict-note-spacing", "uniform-stretching", ], "span-bar-interface": [ "glyph-name", ], "spanner-interface": [ "minimum-length", "normalized-endpoints", "spanner-id", "to-barline", ], "staff-grouper-interface": [ "staff-staff-spacing", "staffgroup-staff-spacing", ], "staff-spacing-interface": [ "stem-spacing-correction", ], "staff-symbol-interface": [ "ledger-extra", "ledger-line-thickness", "ledger-positions", "line-count", "line-positions", "staff-space", "thickness", "width", ], "staff-symbol-referencer-interface": [ "staff-position", ], "stanza-number-interface": [], "stem-interface": [ "avoid-note-head", "beaming", "beamlet-default-length", "beamlet-max-length-proportion", "default-direction", "details", "direction", "duration-log", "french-beaming", "length", "length-fraction", "max-beam-connect", "neutral-direction", "no-stem-extend", "positioning-done", "stem-begin-position", "stemlet-length", "thickness", ], "stem-tremolo-interface": [ "beam-thickness", "beam-width", "direction", "flag-count", "length-fraction", "slope", "style", ], "string-number-interface": [], "stroke-finger-interface": [ "digit-names", ], "system-interface": [ "labels", ], "system-start-delimiter-interface": [ "collapse-height", "style", "thickness", ], "system-start-text-interface": [ "long-text", "self-alignment-X", "self-alignment-Y", "text", ], "tab-note-head-interface": [ "details", ], "text-interface": [ "baseline-skip", "replacement-alist", "text", "text-direction", "word-space", ], "text-script-interface": [ "avoid-slur", "script-priority", ], "tie-column-interface": [ "positioning-done", "tie-configuration", ], "tie-interface": [ "annotation", "avoid-slur", "control-points", "dash-definition", "details", "direction", "head-direction", "line-thickness", "neutral-direction", "staff-position", "thickness", ], "time-signature-interface": [ "fraction", "style", ], "trill-pitch-accidental-interface": [], "trill-spanner-interface": [], "tuplet-bracket-interface": [ "X-positions", "avoid-scripts", "bracket-flare", "bracket-visibility", "break-overshoot", "connect-to-neighbor", "direction", "edge-height", "edge-text", "full-length-padding", "full-length-to-extent", "gap", "padding", "positions", "shorten-pair", "staff-padding", "thickness", ], "tuplet-number-interface": [ "avoid-slur", "direction", ], "unbreakable-spanner-interface": [ "breakable", ], "vaticana-ligature-interface": [ "glyph-name", "thickness", ], "volta-bracket-interface": [ "height", "shorten-pair", "thickness", ], "volta-interface": [], } grobs = { "Accidental": [ "accidental-interface", "font-interface", "grob-interface", "inline-accidental-interface", "item-interface", ], "AccidentalCautionary": [ "accidental-interface", "font-interface", "grob-interface", "inline-accidental-interface", "item-interface", ], "AccidentalPlacement": [ "accidental-placement-interface", "grob-interface", "item-interface", ], "AccidentalSuggestion": [ "accidental-interface", "accidental-suggestion-interface", "font-interface", "grob-interface", "item-interface", "script-interface", "self-alignment-interface", "side-position-interface", ], "Ambitus": [ "ambitus-interface", "axis-group-interface", "break-aligned-interface", "grob-interface", "item-interface", ], "AmbitusAccidental": [ "accidental-interface", "break-aligned-interface", "font-interface", "grob-interface", "item-interface", "side-position-interface", ], "AmbitusLine": [ "ambitus-interface", "font-interface", "grob-interface", "item-interface", ], "AmbitusNoteHead": [ "ambitus-interface", "font-interface", "grob-interface", "item-interface", "ledgered-interface", "note-head-interface", "rhythmic-head-interface", "staff-symbol-referencer-interface", ], "Arpeggio": [ "arpeggio-interface", "font-interface", "grob-interface", "item-interface", "side-position-interface", "staff-symbol-referencer-interface", ], "BalloonTextItem": [ "balloon-interface", "font-interface", "grob-interface", "item-interface", "text-interface", ], "BarLine": [ "bar-line-interface", "break-aligned-interface", "font-interface", "grob-interface", "item-interface", "pure-from-neighbor-interface", ], "BarNumber": [ "break-alignable-interface", "font-interface", "grob-interface", "item-interface", "self-alignment-interface", "side-position-interface", "text-interface", ], "BassFigure": [ "bass-figure-interface", "font-interface", "grob-interface", "item-interface", "rhythmic-grob-interface", "text-interface", ], "BassFigureAlignment": [ "align-interface", "axis-group-interface", "bass-figure-alignment-interface", "grob-interface", "spanner-interface", ], "BassFigureAlignmentPositioning": [ "axis-group-interface", "grob-interface", "side-position-interface", "spanner-interface", ], "BassFigureBracket": [ "enclosing-bracket-interface", "grob-interface", "item-interface", ], "BassFigureContinuation": [ "figured-bass-continuation-interface", "grob-interface", "spanner-interface", ], "BassFigureLine": [ "axis-group-interface", "grob-interface", "spanner-interface", ], "Beam": [ "beam-interface", "font-interface", "grob-interface", "spanner-interface", "staff-symbol-referencer-interface", "unbreakable-spanner-interface", ], "BendAfter": [ "bend-after-interface", "grob-interface", "spanner-interface", ], "BreakAlignGroup": [ "axis-group-interface", "break-aligned-interface", "grob-interface", "item-interface", ], "BreakAlignment": [ "axis-group-interface", "break-alignment-interface", "grob-interface", "item-interface", ], "BreathingSign": [ "break-aligned-interface", "breathing-sign-interface", "font-interface", "grob-interface", "item-interface", "text-interface", ], "ChordName": [ "chord-name-interface", "font-interface", "grob-interface", "item-interface", "rhythmic-grob-interface", "text-interface", ], "Clef": [ "break-aligned-interface", "clef-interface", "font-interface", "grob-interface", "item-interface", "pure-from-neighbor-interface", "staff-symbol-referencer-interface", ], "ClefModifier": [ "clef-modifier-interface", "font-interface", "grob-interface", "item-interface", "self-alignment-interface", "side-position-interface", "text-interface", ], "ClusterSpanner": [ "cluster-interface", "grob-interface", "spanner-interface", ], "ClusterSpannerBeacon": [ "cluster-beacon-interface", "grob-interface", "item-interface", "rhythmic-grob-interface", ], "CombineTextScript": [ "font-interface", "grob-interface", "item-interface", "side-position-interface", "text-interface", "text-script-interface", ], "CueClef": [ "break-aligned-interface", "clef-interface", "font-interface", "grob-interface", "item-interface", "pure-from-neighbor-interface", "staff-symbol-referencer-interface", ], "CueEndClef": [ "break-aligned-interface", "clef-interface", "font-interface", "grob-interface", "item-interface", "pure-from-neighbor-interface", "staff-symbol-referencer-interface", ], "Custos": [ "break-aligned-interface", "custos-interface", "font-interface", "grob-interface", "item-interface", "staff-symbol-referencer-interface", ], "DotColumn": [ "axis-group-interface", "dot-column-interface", "grob-interface", "item-interface", ], "Dots": [ "dots-interface", "font-interface", "grob-interface", "item-interface", "staff-symbol-referencer-interface", ], "DoublePercentRepeat": [ "break-aligned-interface", "font-interface", "grob-interface", "item-interface", "percent-repeat-interface", "percent-repeat-item-interface", ], "DoublePercentRepeatCounter": [ "font-interface", "grob-interface", "item-interface", "percent-repeat-interface", "percent-repeat-item-interface", "self-alignment-interface", "side-position-interface", "text-interface", ], "DoubleRepeatSlash": [ "font-interface", "grob-interface", "item-interface", "percent-repeat-interface", "percent-repeat-item-interface", "rhythmic-grob-interface", ], "DynamicLineSpanner": [ "axis-group-interface", "dynamic-interface", "dynamic-line-spanner-interface", "grob-interface", "side-position-interface", "spanner-interface", ], "DynamicText": [ "dynamic-interface", "dynamic-text-interface", "font-interface", "grob-interface", "item-interface", "script-interface", "self-alignment-interface", "text-interface", ], "DynamicTextSpanner": [ "dynamic-interface", "dynamic-text-spanner-interface", "font-interface", "grob-interface", "line-interface", "line-spanner-interface", "spanner-interface", "text-interface", ], "Episema": [ "episema-interface", "font-interface", "grob-interface", "line-interface", "line-spanner-interface", "side-position-interface", "spanner-interface", ], "Fingering": [ "finger-interface", "font-interface", "grob-interface", "item-interface", "self-alignment-interface", "side-position-interface", "text-interface", "text-script-interface", ], "FingeringColumn": [ "fingering-column-interface", "grob-interface", "item-interface", ], "Flag": [ "flag-interface", "font-interface", "grob-interface", "item-interface", ], "FootnoteItem": [ "balloon-interface", "font-interface", "footnote-interface", "grob-interface", "item-interface", "text-interface", ], "FootnoteSpanner": [ "balloon-interface", "font-interface", "footnote-interface", "footnote-spanner-interface", "grob-interface", "spanner-interface", "text-interface", ], "FretBoard": [ "chord-name-interface", "font-interface", "fret-diagram-interface", "grob-interface", "item-interface", "rhythmic-grob-interface", ], "Glissando": [ "glissando-interface", "grob-interface", "line-interface", "line-spanner-interface", "spanner-interface", "unbreakable-spanner-interface", ], "GraceSpacing": [ "grace-spacing-interface", "grob-interface", "spacing-options-interface", "spanner-interface", ], "GridLine": [ "grid-line-interface", "grob-interface", "item-interface", "self-alignment-interface", ], "GridPoint": [ "grid-point-interface", "grob-interface", "item-interface", ], "Hairpin": [ "dynamic-interface", "grob-interface", "hairpin-interface", "line-interface", "self-alignment-interface", "spanner-interface", ], "HorizontalBracket": [ "grob-interface", "horizontal-bracket-interface", "line-interface", "side-position-interface", "spanner-interface", ], "InstrumentName": [ "font-interface", "grob-interface", "self-alignment-interface", "side-position-interface", "spanner-interface", "system-start-text-interface", ], "InstrumentSwitch": [ "font-interface", "grob-interface", "item-interface", "self-alignment-interface", "side-position-interface", "text-interface", ], "KeyCancellation": [ "break-aligned-interface", "font-interface", "grob-interface", "item-interface", "key-cancellation-interface", "key-signature-interface", "pure-from-neighbor-interface", "staff-symbol-referencer-interface", ], "KeySignature": [ "break-aligned-interface", "font-interface", "grob-interface", "item-interface", "key-signature-interface", "pure-from-neighbor-interface", "staff-symbol-referencer-interface", ], "KievanLigature": [ "font-interface", "grob-interface", "kievan-ligature-interface", "spanner-interface", ], "LaissezVibrerTie": [ "grob-interface", "item-interface", "semi-tie-interface", ], "LaissezVibrerTieColumn": [ "grob-interface", "item-interface", "semi-tie-column-interface", ], "LedgerLineSpanner": [ "grob-interface", "ledger-line-spanner-interface", "spanner-interface", ], "LeftEdge": [ "break-aligned-interface", "grob-interface", "item-interface", ], "LigatureBracket": [ "grob-interface", "line-interface", "spanner-interface", "tuplet-bracket-interface", ], "LyricExtender": [ "grob-interface", "lyric-extender-interface", "lyric-interface", "spanner-interface", ], "LyricHyphen": [ "font-interface", "grob-interface", "lyric-hyphen-interface", "lyric-interface", "spanner-interface", ], "LyricSpace": [ "grob-interface", "lyric-hyphen-interface", "spanner-interface", ], "LyricText": [ "font-interface", "grob-interface", "item-interface", "lyric-syllable-interface", "rhythmic-grob-interface", "self-alignment-interface", "text-interface", ], "MeasureCounter": [ "font-interface", "grob-interface", "measure-counter-interface", "self-alignment-interface", "side-position-interface", "spanner-interface", "text-interface", ], "MeasureGrouping": [ "grob-interface", "measure-grouping-interface", "side-position-interface", "spanner-interface", ], "MelodyItem": [ "grob-interface", "item-interface", "melody-spanner-interface", ], "MensuralLigature": [ "font-interface", "grob-interface", "mensural-ligature-interface", "spanner-interface", ], "MetronomeMark": [ "break-alignable-interface", "font-interface", "grob-interface", "item-interface", "metronome-mark-interface", "self-alignment-interface", "side-position-interface", "text-interface", ], "MultiMeasureRest": [ "font-interface", "grob-interface", "multi-measure-interface", "multi-measure-rest-interface", "rest-interface", "spanner-interface", "staff-symbol-referencer-interface", ], "MultiMeasureRestNumber": [ "font-interface", "grob-interface", "multi-measure-interface", "self-alignment-interface", "side-position-interface", "spanner-interface", "text-interface", ], "MultiMeasureRestText": [ "font-interface", "grob-interface", "multi-measure-interface", "self-alignment-interface", "side-position-interface", "spanner-interface", "text-interface", ], "NonMusicalPaperColumn": [ "axis-group-interface", "font-interface", "grob-interface", "item-interface", "paper-column-interface", "separation-item-interface", "spaceable-grob-interface", ], "NoteCollision": [ "axis-group-interface", "grob-interface", "item-interface", "note-collision-interface", ], "NoteColumn": [ "axis-group-interface", "grob-interface", "item-interface", "note-column-interface", "separation-item-interface", ], "NoteHead": [ "font-interface", "gregorian-ligature-interface", "grob-interface", "item-interface", "ledgered-interface", "ligature-head-interface", "mensural-ligature-interface", "note-head-interface", "rhythmic-grob-interface", "rhythmic-head-interface", "staff-symbol-referencer-interface", "vaticana-ligature-interface", ], "NoteName": [ "font-interface", "grob-interface", "item-interface", "note-name-interface", "text-interface", ], "NoteSpacing": [ "grob-interface", "item-interface", "note-spacing-interface", "spacing-interface", ], "OttavaBracket": [ "font-interface", "grob-interface", "horizontal-bracket-interface", "line-interface", "ottava-bracket-interface", "side-position-interface", "spanner-interface", "text-interface", ], "PaperColumn": [ "axis-group-interface", "font-interface", "grob-interface", "item-interface", "paper-column-interface", "separation-item-interface", "spaceable-grob-interface", ], "ParenthesesItem": [ "font-interface", "grob-interface", "item-interface", "parentheses-interface", ], "PercentRepeat": [ "font-interface", "grob-interface", "multi-measure-rest-interface", "percent-repeat-interface", "spanner-interface", ], "PercentRepeatCounter": [ "font-interface", "grob-interface", "percent-repeat-interface", "self-alignment-interface", "side-position-interface", "spanner-interface", "text-interface", ], "PhrasingSlur": [ "grob-interface", "slur-interface", "spanner-interface", ], "PianoPedalBracket": [ "grob-interface", "line-interface", "piano-pedal-bracket-interface", "piano-pedal-interface", "spanner-interface", ], "RehearsalMark": [ "break-alignable-interface", "font-interface", "grob-interface", "item-interface", "mark-interface", "self-alignment-interface", "side-position-interface", "text-interface", ], "RepeatSlash": [ "grob-interface", "item-interface", "percent-repeat-interface", "percent-repeat-item-interface", "rhythmic-grob-interface", ], "RepeatTie": [ "grob-interface", "item-interface", "semi-tie-interface", ], "RepeatTieColumn": [ "grob-interface", "item-interface", "semi-tie-column-interface", ], "Rest": [ "font-interface", "grob-interface", "item-interface", "rest-interface", "rhythmic-grob-interface", "rhythmic-head-interface", "staff-symbol-referencer-interface", ], "RestCollision": [ "grob-interface", "item-interface", "rest-collision-interface", ], "Script": [ "font-interface", "grob-interface", "item-interface", "script-interface", "side-position-interface", ], "ScriptColumn": [ "grob-interface", "item-interface", "script-column-interface", ], "ScriptRow": [ "grob-interface", "item-interface", "script-column-interface", ], "Slur": [ "grob-interface", "slur-interface", "spanner-interface", ], "SostenutoPedal": [ "font-interface", "grob-interface", "item-interface", "piano-pedal-script-interface", "self-alignment-interface", "text-interface", ], "SostenutoPedalLineSpanner": [ "axis-group-interface", "grob-interface", "piano-pedal-interface", "side-position-interface", "spanner-interface", ], "SpacingSpanner": [ "grob-interface", "spacing-options-interface", "spacing-spanner-interface", "spanner-interface", ], "SpanBar": [ "bar-line-interface", "font-interface", "grob-interface", "item-interface", "span-bar-interface", ], "SpanBarStub": [ "grob-interface", "item-interface", "pure-from-neighbor-interface", ], "StaffGrouper": [ "grob-interface", "spanner-interface", "staff-grouper-interface", ], "StaffSpacing": [ "grob-interface", "item-interface", "spacing-interface", "staff-spacing-interface", ], "StaffSymbol": [ "grob-interface", "spanner-interface", "staff-symbol-interface", ], "StanzaNumber": [ "font-interface", "grob-interface", "item-interface", "side-position-interface", "stanza-number-interface", "text-interface", ], "Stem": [ "grob-interface", "item-interface", "stem-interface", ], "StemStub": [ "grob-interface", "item-interface", ], "StemTremolo": [ "grob-interface", "item-interface", "self-alignment-interface", "stem-tremolo-interface", ], "StringNumber": [ "font-interface", "grob-interface", "item-interface", "self-alignment-interface", "side-position-interface", "string-number-interface", "text-interface", "text-script-interface", ], "StrokeFinger": [ "font-interface", "grob-interface", "item-interface", "self-alignment-interface", "side-position-interface", "stroke-finger-interface", "text-interface", "text-script-interface", ], "SustainPedal": [ "font-interface", "grob-interface", "item-interface", "piano-pedal-interface", "piano-pedal-script-interface", "self-alignment-interface", "text-interface", ], "SustainPedalLineSpanner": [ "axis-group-interface", "grob-interface", "piano-pedal-interface", "side-position-interface", "spanner-interface", ], "System": [ "axis-group-interface", "grob-interface", "spanner-interface", "system-interface", ], "SystemStartBar": [ "grob-interface", "side-position-interface", "spanner-interface", "system-start-delimiter-interface", ], "SystemStartBrace": [ "font-interface", "grob-interface", "side-position-interface", "spanner-interface", "system-start-delimiter-interface", ], "SystemStartBracket": [ "font-interface", "grob-interface", "side-position-interface", "spanner-interface", "system-start-delimiter-interface", ], "SystemStartSquare": [ "font-interface", "grob-interface", "side-position-interface", "spanner-interface", "system-start-delimiter-interface", ], "TabNoteHead": [ "font-interface", "grob-interface", "item-interface", "note-head-interface", "rhythmic-grob-interface", "rhythmic-head-interface", "staff-symbol-referencer-interface", "tab-note-head-interface", "text-interface", ], "TextScript": [ "font-interface", "grob-interface", "instrument-specific-markup-interface", "item-interface", "self-alignment-interface", "side-position-interface", "text-interface", "text-script-interface", ], "TextSpanner": [ "font-interface", "grob-interface", "line-interface", "line-spanner-interface", "side-position-interface", "spanner-interface", ], "Tie": [ "grob-interface", "spanner-interface", "tie-interface", ], "TieColumn": [ "grob-interface", "spanner-interface", "tie-column-interface", ], "TimeSignature": [ "break-aligned-interface", "font-interface", "grob-interface", "item-interface", "pure-from-neighbor-interface", "time-signature-interface", ], "TrillPitchAccidental": [ "accidental-interface", "font-interface", "grob-interface", "inline-accidental-interface", "item-interface", "side-position-interface", "trill-pitch-accidental-interface", ], "TrillPitchGroup": [ "axis-group-interface", "font-interface", "grob-interface", "item-interface", "note-head-interface", "parentheses-interface", "side-position-interface", ], "TrillPitchHead": [ "font-interface", "grob-interface", "item-interface", "ledgered-interface", "pitched-trill-interface", "rhythmic-head-interface", "staff-symbol-referencer-interface", ], "TrillSpanner": [ "font-interface", "grob-interface", "line-interface", "line-spanner-interface", "side-position-interface", "spanner-interface", "trill-spanner-interface", ], "TupletBracket": [ "grob-interface", "line-interface", "spanner-interface", "tuplet-bracket-interface", ], "TupletNumber": [ "font-interface", "grob-interface", "spanner-interface", "text-interface", "tuplet-number-interface", ], "UnaCordaPedal": [ "font-interface", "grob-interface", "item-interface", "piano-pedal-script-interface", "self-alignment-interface", "text-interface", ], "UnaCordaPedalLineSpanner": [ "axis-group-interface", "grob-interface", "piano-pedal-interface", "side-position-interface", "spanner-interface", ], "VaticanaLigature": [ "font-interface", "grob-interface", "spanner-interface", "vaticana-ligature-interface", ], "VerticalAlignment": [ "align-interface", "axis-group-interface", "grob-interface", "spanner-interface", ], "VerticalAxisGroup": [ "axis-group-interface", "grob-interface", "hara-kiri-group-spanner-interface", "spanner-interface", ], "VoiceFollower": [ "grob-interface", "line-interface", "line-spanner-interface", "spanner-interface", ], "VoltaBracket": [ "font-interface", "grob-interface", "horizontal-bracket-interface", "line-interface", "side-position-interface", "spanner-interface", "text-interface", "volta-bracket-interface", "volta-interface", ], "VoltaBracketSpanner": [ "axis-group-interface", "grob-interface", "side-position-interface", "spanner-interface", "volta-interface", ], } contextproperties = [ "aDueText", "accidentalGrouping", "additionalPitchPrefix", "alignAboveContext", "alignBassFigureAccidentals", "alignBelowContext", "alternativeNumberingStyle", "associatedVoice", "autoAccidentals", "autoBeamCheck", "autoBeaming", "autoCautionaries", "automaticBars", "barAlways", "barCheckSynchronize", "barNumberFormatter", "barNumberVisibility", "baseMoment", "bassFigureFormatFunction", "bassStaffProperties", "beamExceptions", "beamHalfMeasure", "beatStructure", "chordChanges", "chordNameExceptions", "chordNameExceptionsFull", "chordNameExceptionsPartial", "chordNameFunction", "chordNameLowercaseMinor", "chordNameSeparator", "chordNoteNamer", "chordPrefixSpacer", "chordRootNamer", "clefGlyph", "clefPosition", "clefTransposition", "clefTranspositionFormatter", "clefTranspositionStyle", "completionBusy", "completionUnit", "connectArpeggios", "countPercentRepeats", "createKeyOnClefChange", "createSpacing", "crescendoSpanner", "crescendoText", "cueClefGlyph", "cueClefPosition", "cueClefTransposition", "cueClefTranspositionFormatter", "cueClefTranspositionStyle", "currentBarNumber", "decrescendoSpanner", "decrescendoText", "defaultBarType", "defaultStrings", "doubleRepeatSegnoType", "doubleRepeatType", "doubleSlurs", "drumPitchTable", "drumStyleTable", "endRepeatSegnoType", "endRepeatType", "explicitClefVisibility", "explicitCueClefVisibility", "explicitKeySignatureVisibility", "extendersOverRests", "extraNatural", "figuredBassAlterationDirection", "figuredBassCenterContinuations", "figuredBassFormatter", "figuredBassPlusDirection", "fingeringOrientations", "firstClef", "followVoice", "fontSize", "forbidBreak", "forceClef", "fretLabels", "glissandoMap", "gridInterval", "handleNegativeFrets", "harmonicAccidentals", "harmonicDots", "highStringOne", "ignoreBarChecks", "ignoreFiguredBassRest", "ignoreMelismata", "implicitBassFigures", "implicitTimeSignatureVisibility", "includeGraceNotes", "instrumentCueName", "instrumentEqualizer", "instrumentName", "instrumentTransposition", "internalBarNumber", "keepAliveInterfaces", "keyAlterationOrder", "keySignature", "lyricMelismaAlignment", "majorSevenSymbol", "markFormatter", "maximumFretStretch", "measureLength", "measurePosition", "melismaBusyProperties", "metronomeMarkFormatter", "middleCClefPosition", "middleCCuePosition", "middleCOffset", "middleCPosition", "midiBalance", "midiChannelMapping", "midiChorusLevel", "midiInstrument", "midiMaximumVolume", "midiMergeUnisons", "midiMinimumVolume", "midiPanPosition", "midiReverbLevel", "minimumFret", "minimumPageTurnLength", "minimumRepeatLengthForPageTurn", "minorChordModifier", "noChordSymbol", "noteToFretFunction", "ottavation", "output", "partCombineTextsOnNote", "pedalSostenutoStrings", "pedalSostenutoStyle", "pedalSustainStrings", "pedalSustainStyle", "pedalUnaCordaStrings", "pedalUnaCordaStyle", "predefinedDiagramTable", "printKeyCancellation", "printOctaveNames", "printPartCombineTexts", "proportionalNotationDuration", "rehearsalMark", "repeatCommands", "repeatCountVisibility", "restCompletionBusy", "restNumberThreshold", "restrainOpenStrings", "searchForVoice", "segnoType", "shapeNoteStyles", "shortInstrumentName", "shortVocalName", "skipBars", "skipTypesetting", "slashChordSeparator", "soloIIText", "soloText", "squashedPosition", "staffLineLayoutFunction", "stanza", "startRepeatSegnoType", "startRepeatType", "stemLeftBeamCount", "stemRightBeamCount", "strictBeatBeaming", "stringNumberOrientations", "stringOneTopmost", "stringTunings", "strokeFingerOrientations", "subdivideBeams", "suggestAccidentals", "systemStartDelimiter", "systemStartDelimiterHierarchy", "tabStaffLineLayoutFunction", "tablatureFormat", "tempoHideNote", "tempoWholesPerMinute", "tieWaitForNote", "timeSignatureFraction", "timeSignatureSettings", "timing", "tonic", "topLevelAlignment", "trebleStaffProperties", "tremoloFlags", "tupletFullLength", "tupletFullLengthNote", "tupletSpannerDuration", "useBassFigureExtenders", "vocalName", "voltaSpannerDuration", "whichBar", ] engravers = [ "Accidental_engraver", "Ambitus_engraver", "Arpeggio_engraver", "Auto_beam_engraver", "Axis_group_engraver", "Balloon_engraver", "Bar_engraver", "Bar_number_engraver", "Beam_collision_engraver", "Beam_engraver", "Beam_performer", "Bend_engraver", "Break_align_engraver", "Breathing_sign_engraver", "Chord_name_engraver", "Chord_tremolo_engraver", "Clef_engraver", "Cluster_spanner_engraver", "Collision_engraver", "Completion_heads_engraver", "Completion_rest_engraver", "Concurrent_hairpin_engraver", "Control_track_performer", "Cue_clef_engraver", "Custos_engraver", "Default_bar_line_engraver", "Dot_column_engraver", "Dots_engraver", "Double_percent_repeat_engraver", "Drum_note_performer", "Drum_notes_engraver", "Dynamic_align_engraver", "Dynamic_engraver", "Dynamic_performer", "Engraver", "Episema_engraver", "Extender_engraver", "Figured_bass_engraver", "Figured_bass_position_engraver", "Fingering_column_engraver", "Fingering_engraver", "Font_size_engraver", "Footnote_engraver", "Forbid_line_break_engraver", "Fretboard_engraver", "Glissando_engraver", "Grace_auto_beam_engraver", "Grace_beam_engraver", "Grace_engraver", "Grace_spacing_engraver", "Grid_line_span_engraver", "Grid_point_engraver", "Grob_pq_engraver", "Horizontal_bracket_engraver", "Hyphen_engraver", "Instrument_name_engraver", "Instrument_switch_engraver", "Keep_alive_together_engraver", "Key_engraver", "Key_performer", "Kievan_ligature_engraver", "Laissez_vibrer_engraver", "Ledger_line_engraver", "Ligature_bracket_engraver", "Lyric_engraver", "Lyric_performer", "Mark_engraver", "Measure_grouping_engraver", "Melody_engraver", "Mensural_ligature_engraver", "Metronome_mark_engraver", "Midi_control_function_performer", "Multi_measure_rest_engraver", "New_fingering_engraver", "Note_head_line_engraver", "Note_heads_engraver", "Note_name_engraver", "Note_performer", "Note_spacing_engraver", "Ottava_spanner_engraver", "Output_property_engraver", "Page_turn_engraver", "Paper_column_engraver", "Parenthesis_engraver", "Part_combine_engraver", "Percent_repeat_engraver", "Phrasing_slur_engraver", "Piano_pedal_align_engraver", "Piano_pedal_engraver", "Piano_pedal_performer", "Pitch_squash_engraver", "Pitched_trill_engraver", "Pure_from_neighbor_engraver", "Repeat_acknowledge_engraver", "Repeat_tie_engraver", "Rest_collision_engraver", "Rest_engraver", "Rhythmic_column_engraver", "Scheme_engraver", "Script_column_engraver", "Script_engraver", "Script_row_engraver", "Separating_line_group_engraver", "Slash_repeat_engraver", "Slur_engraver", "Slur_performer", "Spacing_engraver", "Span_arpeggio_engraver", "Span_bar_engraver", "Span_bar_stub_engraver", "Spanner_break_forbid_engraver", "Staff_collecting_engraver", "Staff_performer", "Staff_symbol_engraver", "Stanza_number_align_engraver", "Stanza_number_engraver", "Stem_engraver", "System_start_delimiter_engraver", "Tab_note_heads_engraver", "Tab_staff_symbol_engraver", "Tab_tie_follow_engraver", "Tempo_performer", "Text_engraver", "Text_spanner_engraver", "Tie_engraver", "Tie_performer", "Time_signature_engraver", "Time_signature_performer", "Timing_translator", "Translator", "Trill_spanner_engraver", "Tuplet_engraver", "Tweak_engraver", "Vaticana_ligature_engraver", "Vertical_align_engraver", "Volta_engraver", ] musicglyphs = [ "accidentals.doublesharp", "accidentals.flat", "accidentals.flat.arrowboth", "accidentals.flat.arrowdown", "accidentals.flat.arrowup", "accidentals.flat.slash", "accidentals.flat.slashslash", "accidentals.flatflat", "accidentals.flatflat.slash", "accidentals.hufnagelM1", "accidentals.kievan1", "accidentals.kievanM1", "accidentals.leftparen", "accidentals.medicaeaM1", "accidentals.mensural1", "accidentals.mensuralM1", "accidentals.mirroredflat", "accidentals.mirroredflat.backslash", "accidentals.mirroredflat.flat", "accidentals.natural", "accidentals.natural.arrowboth", "accidentals.natural.arrowdown", "accidentals.natural.arrowup", "accidentals.rightparen", "accidentals.sharp", "accidentals.sharp.arrowboth", "accidentals.sharp.arrowdown", "accidentals.sharp.arrowup", "accidentals.sharp.slashslash.stem", "accidentals.sharp.slashslash.stemstemstem", "accidentals.sharp.slashslashslash.stem", "accidentals.sharp.slashslashslash.stemstem", "accidentals.vaticana0", "accidentals.vaticanaM1", "accordion.bayanbass", "accordion.discant", "accordion.dot", "accordion.freebass", "accordion.oldEE", "accordion.pull", "accordion.push", "accordion.stdbass", "arrowheads.close.01", "arrowheads.close.0M1", "arrowheads.close.11", "arrowheads.close.1M1", "arrowheads.open.01", "arrowheads.open.0M1", "arrowheads.open.11", "arrowheads.open.1M1", "brackettips.down", "brackettips.up", "clefs.C", "clefs.C_change", "clefs.F", "clefs.F_change", "clefs.G", "clefs.G_change", "clefs.blackmensural.c", "clefs.blackmensural.c_change", "clefs.hufnagel.do", "clefs.hufnagel.do.fa", "clefs.hufnagel.do.fa_change", "clefs.hufnagel.do_change", "clefs.hufnagel.fa", "clefs.hufnagel.fa_change", "clefs.kievan.do", "clefs.kievan.do_change", "clefs.medicaea.do", "clefs.medicaea.do_change", "clefs.medicaea.fa", "clefs.medicaea.fa_change", "clefs.mensural.c", "clefs.mensural.c_change", "clefs.mensural.f", "clefs.mensural.f_change", "clefs.mensural.g", "clefs.mensural.g_change", "clefs.neomensural.c", "clefs.neomensural.c_change", "clefs.percussion", "clefs.percussion_change", "clefs.petrucci.c1", "clefs.petrucci.c1_change", "clefs.petrucci.c2", "clefs.petrucci.c2_change", "clefs.petrucci.c3", "clefs.petrucci.c3_change", "clefs.petrucci.c4", "clefs.petrucci.c4_change", "clefs.petrucci.c5", "clefs.petrucci.c5_change", "clefs.petrucci.f", "clefs.petrucci.f_change", "clefs.petrucci.g", "clefs.petrucci.g_change", "clefs.tab", "clefs.tab_change", "clefs.vaticana.do", "clefs.vaticana.do_change", "clefs.vaticana.fa", "clefs.vaticana.fa_change", "comma", "custodes.hufnagel.d0", "custodes.hufnagel.d1", "custodes.hufnagel.d2", "custodes.hufnagel.u0", "custodes.hufnagel.u1", "custodes.hufnagel.u2", "custodes.medicaea.d0", "custodes.medicaea.d1", "custodes.medicaea.d2", "custodes.medicaea.u0", "custodes.medicaea.u1", "custodes.medicaea.u2", "custodes.mensural.d0", "custodes.mensural.d1", "custodes.mensural.d2", "custodes.mensural.u0", "custodes.mensural.u1", "custodes.mensural.u2", "custodes.vaticana.d0", "custodes.vaticana.d1", "custodes.vaticana.d2", "custodes.vaticana.u0", "custodes.vaticana.u1", "custodes.vaticana.u2", "dots.dot", "dots.dotkievan", "dots.dotvaticana", "eight", "f", "five", "flags.d3", "flags.d4", "flags.d5", "flags.d6", "flags.d7", "flags.dgrace", "flags.mensurald03", "flags.mensurald04", "flags.mensurald05", "flags.mensurald06", "flags.mensurald13", "flags.mensurald14", "flags.mensurald15", "flags.mensurald16", "flags.mensurald23", "flags.mensurald24", "flags.mensurald25", "flags.mensurald26", "flags.mensuralu03", "flags.mensuralu04", "flags.mensuralu05", "flags.mensuralu06", "flags.mensuralu13", "flags.mensuralu14", "flags.mensuralu15", "flags.mensuralu16", "flags.mensuralu23", "flags.mensuralu24", "flags.mensuralu25", "flags.mensuralu26", "flags.u3", "flags.u4", "flags.u5", "flags.u6", "flags.u7", "flags.ugrace", "four", "hyphen", "m", "nine", "noteheads.d0doFunk", "noteheads.d0fa", "noteheads.d0faFunk", "noteheads.d0faThin", "noteheads.d0miFunk", "noteheads.d0reFunk", "noteheads.d0tiFunk", "noteheads.d1do", "noteheads.d1doFunk", "noteheads.d1doThin", "noteheads.d1doWalker", "noteheads.d1fa", "noteheads.d1faFunk", "noteheads.d1faThin", "noteheads.d1faWalker", "noteheads.d1miFunk", "noteheads.d1re", "noteheads.d1reFunk", "noteheads.d1reThin", "noteheads.d1reWalker", "noteheads.d1ti", "noteheads.d1tiFunk", "noteheads.d1tiThin", "noteheads.d1tiWalker", "noteheads.d1triangle", "noteheads.d2do", "noteheads.d2doFunk", "noteheads.d2doThin", "noteheads.d2doWalker", "noteheads.d2fa", "noteheads.d2faFunk", "noteheads.d2faThin", "noteheads.d2faWalker", "noteheads.d2kievan", "noteheads.d2re", "noteheads.d2reFunk", "noteheads.d2reThin", "noteheads.d2reWalker", "noteheads.d2ti", "noteheads.d2tiFunk", "noteheads.d2tiThin", "noteheads.d2tiWalker", "noteheads.d2triangle", "noteheads.d3kievan", "noteheads.dM2", "noteheads.dM2blackmensural", "noteheads.dM2mensural", "noteheads.dM2neomensural", "noteheads.dM2semimensural", "noteheads.dM3blackmensural", "noteheads.dM3mensural", "noteheads.dM3neomensural", "noteheads.dM3semimensural", "noteheads.drM2mensural", "noteheads.drM2neomensural", "noteheads.drM2semimensural", "noteheads.drM3mensural", "noteheads.drM3neomensural", "noteheads.drM3semimensural", "noteheads.s0", "noteheads.s0blackmensural", "noteheads.s0blackpetrucci", "noteheads.s0cross", "noteheads.s0diamond", "noteheads.s0do", "noteheads.s0doThin", "noteheads.s0doWalker", "noteheads.s0faWalker", "noteheads.s0harmonic", "noteheads.s0kievan", "noteheads.s0la", "noteheads.s0laFunk", "noteheads.s0laThin", "noteheads.s0laWalker", "noteheads.s0mensural", "noteheads.s0mi", "noteheads.s0miMirror", "noteheads.s0miThin", "noteheads.s0miWalker", "noteheads.s0neomensural", "noteheads.s0petrucci", "noteheads.s0re", "noteheads.s0reThin", "noteheads.s0reWalker", "noteheads.s0slash", "noteheads.s0sol", "noteheads.s0solFunk", "noteheads.s0ti", "noteheads.s0tiThin", "noteheads.s0tiWalker", "noteheads.s0triangle", "noteheads.s1", "noteheads.s1blackpetrucci", "noteheads.s1cross", "noteheads.s1diamond", "noteheads.s1kievan", "noteheads.s1la", "noteheads.s1laFunk", "noteheads.s1laThin", "noteheads.s1laWalker", "noteheads.s1mensural", "noteheads.s1mi", "noteheads.s1miMirror", "noteheads.s1miThin", "noteheads.s1miWalker", "noteheads.s1neomensural", "noteheads.s1petrucci", "noteheads.s1slash", "noteheads.s1sol", "noteheads.s1solFunk", "noteheads.s2", "noteheads.s2blackpetrucci", "noteheads.s2cross", "noteheads.s2diamond", "noteheads.s2harmonic", "noteheads.s2la", "noteheads.s2laFunk", "noteheads.s2laThin", "noteheads.s2laWalker", "noteheads.s2mensural", "noteheads.s2mi", "noteheads.s2miFunk", "noteheads.s2miMirror", "noteheads.s2miThin", "noteheads.s2miWalker", "noteheads.s2neomensural", "noteheads.s2petrucci", "noteheads.s2slash", "noteheads.s2sol", "noteheads.s2solFunk", "noteheads.s2xcircle", "noteheads.sM1", "noteheads.sM1blackmensural", "noteheads.sM1double", "noteheads.sM1kievan", "noteheads.sM1mensural", "noteheads.sM1neomensural", "noteheads.sM1semimensural", "noteheads.sM2blackligmensural", "noteheads.sM2kievan", "noteheads.sM2ligmensural", "noteheads.sM2semiligmensural", "noteheads.sM3blackligmensural", "noteheads.sM3ligmensural", "noteheads.sM3semiligmensural", "noteheads.shufnagel.lpes", "noteheads.shufnagel.punctum", "noteheads.shufnagel.virga", "noteheads.smedicaea.inclinatum", "noteheads.smedicaea.punctum", "noteheads.smedicaea.rvirga", "noteheads.smedicaea.virga", "noteheads.sr1kievan", "noteheads.srM1mensural", "noteheads.srM1neomensural", "noteheads.srM1semimensural", "noteheads.srM2ligmensural", "noteheads.srM2semiligmensural", "noteheads.srM3ligmensural", "noteheads.srM3semiligmensural", "noteheads.ssolesmes.auct.asc", "noteheads.ssolesmes.auct.desc", "noteheads.ssolesmes.incl.auctum", "noteheads.ssolesmes.incl.parvum", "noteheads.ssolesmes.oriscus", "noteheads.ssolesmes.stropha", "noteheads.ssolesmes.stropha.aucta", "noteheads.svaticana.cephalicus", "noteheads.svaticana.epiphonus", "noteheads.svaticana.inclinatum", "noteheads.svaticana.inner.cephalicus", "noteheads.svaticana.linea.punctum", "noteheads.svaticana.linea.punctum.cavum", "noteheads.svaticana.lpes", "noteheads.svaticana.plica", "noteheads.svaticana.punctum", "noteheads.svaticana.punctum.cavum", "noteheads.svaticana.quilisma", "noteheads.svaticana.reverse.plica", "noteheads.svaticana.reverse.vplica", "noteheads.svaticana.upes", "noteheads.svaticana.vepiphonus", "noteheads.svaticana.vlpes", "noteheads.svaticana.vplica", "noteheads.svaticana.vupes", "noteheads.u0doFunk", "noteheads.u0fa", "noteheads.u0faFunk", "noteheads.u0faThin", "noteheads.u0miFunk", "noteheads.u0reFunk", "noteheads.u0tiFunk", "noteheads.u1do", "noteheads.u1doFunk", "noteheads.u1doThin", "noteheads.u1doWalker", "noteheads.u1fa", "noteheads.u1faFunk", "noteheads.u1faThin", "noteheads.u1faWalker", "noteheads.u1miFunk", "noteheads.u1re", "noteheads.u1reFunk", "noteheads.u1reThin", "noteheads.u1reWalker", "noteheads.u1ti", "noteheads.u1tiFunk", "noteheads.u1tiThin", "noteheads.u1tiWalker", "noteheads.u1triangle", "noteheads.u2do", "noteheads.u2doFunk", "noteheads.u2doThin", "noteheads.u2doWalker", "noteheads.u2fa", "noteheads.u2faFunk", "noteheads.u2faThin", "noteheads.u2faWalker", "noteheads.u2kievan", "noteheads.u2re", "noteheads.u2reFunk", "noteheads.u2reThin", "noteheads.u2reWalker", "noteheads.u2ti", "noteheads.u2tiFunk", "noteheads.u2tiThin", "noteheads.u2tiWalker", "noteheads.u2triangle", "noteheads.u3kievan", "noteheads.uM2", "noteheads.uM2blackmensural", "noteheads.uM2mensural", "noteheads.uM2neomensural", "noteheads.uM2semimensural", "noteheads.uM3blackmensural", "noteheads.uM3mensural", "noteheads.uM3neomensural", "noteheads.uM3semimensural", "noteheads.urM2mensural", "noteheads.urM2neomensural", "noteheads.urM2semimensural", "noteheads.urM3mensural", "noteheads.urM3neomensural", "noteheads.urM3semimensural", "one", "p", "pedal.*", "pedal..", "pedal.M", "pedal.P", "pedal.Ped", "pedal.d", "pedal.e", "period", "plus", "r", "rests.0", "rests.0mensural", "rests.0neomensural", "rests.0o", "rests.1", "rests.1mensural", "rests.1neomensural", "rests.1o", "rests.2", "rests.2classical", "rests.2mensural", "rests.2neomensural", "rests.3", "rests.3mensural", "rests.3neomensural", "rests.4", "rests.4mensural", "rests.4neomensural", "rests.5", "rests.6", "rests.7", "rests.M1", "rests.M1mensural", "rests.M1neomensural", "rests.M1o", "rests.M2", "rests.M2mensural", "rests.M2neomensural", "rests.M3", "rests.M3mensural", "rests.M3neomensural", "s", "scripts.arpeggio", "scripts.arpeggio.arrow.1", "scripts.arpeggio.arrow.M1", "scripts.augmentum", "scripts.barline.kievan", "scripts.caesura.curved", "scripts.caesura.straight", "scripts.circulus", "scripts.coda", "scripts.daccentus", "scripts.dfermata", "scripts.dlongfermata", "scripts.dmarcato", "scripts.downbow", "scripts.downmordent", "scripts.downprall", "scripts.dpedalheel", "scripts.dpedaltoe", "scripts.dportato", "scripts.dsemicirculus", "scripts.dshortfermata", "scripts.dsignumcongruentiae", "scripts.dstaccatissimo", "scripts.dverylongfermata", "scripts.espr", "scripts.flageolet", "scripts.halfopen", "scripts.halfopenvertical", "scripts.ictus", "scripts.lcomma", "scripts.lineprall", "scripts.lvarcomma", "scripts.mordent", "scripts.open", "scripts.prall", "scripts.pralldown", "scripts.prallmordent", "scripts.prallprall", "scripts.prallup", "scripts.rcomma", "scripts.reverseturn", "scripts.rvarcomma", "scripts.segno", "scripts.sforzato", "scripts.snappizzicato", "scripts.staccato", "scripts.stopped", "scripts.tenuto", "scripts.thumb", "scripts.tickmark", "scripts.trilelement", "scripts.trill", "scripts.trill_element", "scripts.turn", "scripts.uaccentus", "scripts.ufermata", "scripts.ulongfermata", "scripts.umarcato", "scripts.upbow", "scripts.upedalheel", "scripts.upedaltoe", "scripts.upmordent", "scripts.uportato", "scripts.upprall", "scripts.usemicirculus", "scripts.ushortfermata", "scripts.usignumcongruentiae", "scripts.ustaccatissimo", "scripts.uverylongfermata", "scripts.varcoda", "scripts.varsegno", "seven", "six", "space", "three", "ties.lyric.default", "ties.lyric.short", "timesig.C22", "timesig.C44", "timesig.mensural22", "timesig.mensural24", "timesig.mensural32", "timesig.mensural34", "timesig.mensural44", "timesig.mensural48", "timesig.mensural64", "timesig.mensural68", "timesig.mensural68alt", "timesig.mensural94", "timesig.mensural98", "timesig.neomensural22", "timesig.neomensural24", "timesig.neomensural32", "timesig.neomensural34", "timesig.neomensural44", "timesig.neomensural48", "timesig.neomensural64", "timesig.neomensural68", "timesig.neomensural68alt", "timesig.neomensural94", "timesig.neomensural98", "two", "z", "zero", ] python-ly-0.9.3/ly/data/makeschemedata.py0000644000175000017500000001161412501542536021501 0ustar wilbertwilbert00000000000000#!/usr/bin/env python # This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ generate all scheme words in the _scheme_data.py file """ from __future__ import unicode_literals import os import re try: from urllib.request import urlopen except: from urllib import urlopen VERSION = "2.18" GUILE_URL = "https://www.gnu.org/software/guile/docs/docs-1.8/guile-ref/Procedure-Index.html#Procedure-Index" LY_FUNCTION_URL = "http://lilypond.org/doc/v2.18/Documentation/internals/scheme-functions" SCM_PATH = os.path.join(os.environ['HOME'], 'lilypond_bin/2.18.2-1/lilypond/usr/share/lilypond/current/scm') GUILE_IGNONED_TYPE = ( 'Apply Traps', 'Common Trap Options', 'Continuations', 'Debug on Error', 'Display Backtrace', 'Encryption', 'Entry Traps', 'Exit Traps', 'Garbage Collection Functions', 'getopt-long Reference', 'Internationalization', 'Location Traps', 'Network Databases', 'Network Socket Address', 'Network Sockets and Communication', 'Procedure Traps', 'Signals', 'Threads', 'Trap Utilities', 'Source Traps', 'Step Traps', 'SRFI-37', 'Stepping and Continuing', 'System asyncs', 'System Identification', 'User asyncs', 'User Information', 'User level options interfaces', ) GUILE_OTHER_WORDS = ['else', 'set!'] def writeList(file_, name, lst): file_.write("{} = [\n".format(name)) for word in sorted(set(lst)): file_.write(" '{0}',\n".format(word)) file_.write("]\n\n") def replace(word): word = word.replace('<', '<') word = word.replace('>', '>') return word guilePage = urlopen(GUILE_URL).read() guilePat = re.compile(r"([a-z\d\+\?\!\*&;/=:-]+): (.+)") lyFunctionPage = urlopen(LY_FUNCTION_URL).read() lyFunctionPat = re.compile(r'Function: (ly:.+)') defineFunc = re.compile(r'\((?:define\*?|define-safe)-public\s+\(([->""" doctype_txt = """""" python-ly-0.9.3/ly/musicxml/lymus2musxml.py0000664000175000017500000006342712635547305022220 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Using the tree structure from ly.music to initiate the conversion to MusicXML. Uses functions similar to ly.music.items.Document.iter_music() to iter through the node tree. But information about where a node branch ends is also added. During the iteration the information needed for the conversion is captured. """ from __future__ import unicode_literals from __future__ import print_function import ly.document import ly.music from . import create_musicxml from . import ly2xml_mediator from . import xml_objs #excluded from parsing excl_list = ['Version', 'Midi', 'Layout'] # Defining contexts in relation to musicXML group_contexts = ['StaffGroup', 'ChoirStaff'] pno_contexts = ['PianoStaff', 'GrandStaff'] staff_contexts = ['Staff', 'RhythmicStaff', 'TabStaff', 'DrumStaff', 'VaticanaStaff', 'MensuralStaff'] part_contexts = pno_contexts + staff_contexts class End(): """ Extra class that gives information about the end of Container elements in the node list. """ def __init__(self, node): self.node = node def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.node) class ParseSource(): """ creates the XML-file from the source code according to the Music XML standard """ def __init__(self): self.musxml = create_musicxml.CreateMusicXML() self.mediator = ly2xml_mediator.Mediator() self.relative = False self.tuplet = [] self.scale = '' self.grace_seq = False self.trem_rep = 0 self.piano_staff = 0 self.numericTime = False self.voice_sep = False self.sims_and_seqs = [] self.override_dict = {} self.ottava = False self.with_contxt = None self.schm_assignm = None self.tempo = () self.tremolo = False self.tupl_span = False self.unset_tuplspan = False self.alt_mode = None self.rel_pitch_isset = False self.slurcount = 0 self.slurnr = 0 self.phrslurnr = 0 def parse_text(self, ly_text): """Parse the LilyPond source as text.""" doc = ly.document.Document(ly_text) self.parse_document(doc) def parse_document(self, ly_doc): """Parse the LilyPond source as a ly.document document. The document is converted to absolute mode to facilitate the export. Use parse_text instead if you want the document to be unaffected. """ import ly.pitch.rel2abs cursor = ly.document.Cursor(ly_doc) ly.pitch.rel2abs.rel2abs(cursor) mustree = ly.music.document(ly_doc) self.parse_tree(mustree) def parse_tree(self, mustree): """Parse the LilyPond source as a ly.music node tree.""" # print(mustree.dump()) header_nodes = self.iter_header(mustree) if header_nodes: self.parse_nodes(header_nodes) score = self.get_score(mustree) if score: mus_nodes = self.iter_score(score, mustree) else: mus_nodes = self.find_score_sub(mustree) self.mediator.new_section("fallback") #fallback/default section self.parse_nodes(mus_nodes) def parse_nodes(self, nodes): """Work through all nodes by calling the function with the same name as the nodes class.""" if nodes: for m in nodes: # print(m) func_name = m.__class__.__name__ #get instance name if func_name not in excl_list: try: func_call = getattr(self, func_name) func_call(m) except AttributeError as ae: print("Warning:", func_name, "not implemented!") print(ae) pass else: print("Warning! Couldn't parse source!") def musicxml(self, prettyprint=True): self.mediator.check_score() xml_objs.IterateXmlObjs( self.mediator.score, self.musxml, self.mediator.divisions) xml = self.musxml.musicxml(prettyprint) return xml ## # The different source types from ly.music are here sent to translation. ## def Assignment(self, a): """ Variables should already have been substituted so this need only cover other types of assignments. """ if isinstance(a.value(), ly.music.items.Markup): val = a.value().plaintext() elif isinstance(a.value(), ly.music.items.String): val = a.value().value() elif isinstance(a.value(), ly.music.items.Scheme): val = a.value().get_string() if not val: self.schm_assignm = a.name() if self.look_behind(a, ly.music.items.With): if self.with_contxt in group_contexts: self.mediator.set_by_property(a.name(), val, True) else: self.mediator.set_by_property(a.name(), val) else: self.mediator.new_header_assignment(a.name(), val) def MusicList(self, musicList): if musicList.token == '<<': if self.look_ahead(musicList, ly.music.items.VoiceSeparator): self.mediator.new_snippet('sim-snip') self.voice_sep = True else: self.mediator.new_section('simultan', True) self.sims_and_seqs.append('sim') elif musicList.token == '{': self.sims_and_seqs.append('seq') def Chord(self, chord): self.mediator.clear_chord() def Q(self, q): self.mediator.copy_prev_chord(q.duration) def Context(self, context): r""" \context """ self.in_context = True self.check_context(context.context(), context.context_id(), context.token) def check_context(self, context, context_id=None, token=""): """Check context and do appropriate action (e.g. create new part).""" # Check first if part already exists if context_id: match = self.mediator.get_part_by_id(context_id) if match: self.mediator.new_part(to_part=match) return if context in pno_contexts: self.mediator.new_part(context_id, piano=True) self.piano_staff = 1 elif context in group_contexts: self.mediator.new_group() elif context in staff_contexts: if self.piano_staff: if self.piano_staff > 1: self.mediator.set_voicenr(nr=self.piano_staff+3) self.mediator.new_section('piano-staff'+str(self.piano_staff)) self.mediator.set_staffnr(self.piano_staff) self.piano_staff += 1 else: if token != '\\context' or self.mediator.part_not_empty(): self.mediator.new_part(context_id) self.mediator.add_staff_id(context_id) elif context == 'Voice': self.sims_and_seqs.append('voice') if context_id: self.mediator.new_section(context_id) else: self.mediator.new_section('voice') elif context == 'Devnull': self.mediator.new_section('devnull', True) else: print("Context not implemented:", context) def VoiceSeparator(self, voice_sep): self.mediator.new_snippet('sim') self.mediator.set_voicenr(add=True) def Change(self, change): r""" A \change music expression. Changes the staff number. """ if change.context() == 'Staff': self.mediator.set_staffnr(0, staff_id=change.context_id()) def PipeSymbol(self, barcheck): """ PipeSymbol = | """ self.mediator.new_bar() def Clef(self, clef): r""" Clef \clef""" self.mediator.new_clef(clef.specifier()) def KeySignature(self, key): self.mediator.new_key(key.pitch().output(), key.mode()) def Relative(self, relative): r"""A \relative music expression.""" self.relative = True def Note(self, note): """ notename, e.g. c, cis, a bes ... """ #print(note.token) if note.length(): if self.relative and not self.rel_pitch_isset: self.mediator.new_note(note, False) self.mediator.set_relative(note) self.rel_pitch_isset = True else: self.mediator.new_note(note, self.relative) self.check_note(note) else: if isinstance(note.parent(), ly.music.items.Relative): self.mediator.set_relative(note) self.rel_pitch_isset = True elif isinstance(note.parent(), ly.music.items.Chord): self.mediator.new_chord(note, note.parent().duration, self.relative) # chord as grace note if self.grace_seq: self.mediator.new_chord_grace() def Unpitched(self, unpitched): """A note without pitch, just a standalone duration.""" if unpitched.length(): if self.alt_mode == 'drum': self.mediator.new_iso_dura(unpitched, self.relative, True) else: self.mediator.new_iso_dura(unpitched, self.relative) self.check_note(unpitched) def DrumNote(self, drumnote): """A note in DrumMode.""" if drumnote.length(): self.mediator.new_note(drumnote, is_unpitched=True) self.check_note(drumnote) def check_note(self, note): """Generic check for all notes, both pitched and unpitched.""" if self.tuplet: tlevels = len(self.tuplet) nested = True if tlevels > 1 else False for td in self.tuplet: if nested: self.mediator.change_to_tuplet(td['fraction'], td['ttype'], td['nr'], td['length']) else: self.mediator.change_to_tuplet(td['fraction'], td['ttype'], td['nr']) td['ttype'] = "" self.mediator.check_divs() if self.grace_seq: self.mediator.new_grace() if self.trem_rep and not self.look_ahead(note, ly.music.items.Duration): self.mediator.set_tremolo(trem_type='start', repeats=self.trem_rep) def Duration(self, duration): """A written duration""" if self.tempo: self.mediator.new_tempo(duration.token, duration.tokens, *self.tempo) self.tempo = () elif self.tremolo: self.mediator.set_tremolo(duration=int(duration.token)) self.tremolo = False elif self.tupl_span: self.mediator.set_tuplspan_dur(duration.token, duration.tokens) self.tupl_span = False else: self.mediator.new_duration_token(duration.token, duration.tokens) if self.trem_rep: self.mediator.set_tremolo(trem_type='start', repeats=self.trem_rep) def Tempo(self, tempo): """ Tempo direction, e g '4 = 80' """ if self.look_ahead(tempo, ly.music.items.Duration): self.tempo = (tempo.tempo(), tempo.text()) else: self.mediator.new_tempo(0, (), tempo.tempo(), tempo.text()) def Tie(self, tie): """ tie ~ """ self.mediator.tie_to_next() def Rest(self, rest): r""" rest, r or R. Note: NOT by command, i.e. \rest """ if rest.token == 'R': self.scale = 'R' self.mediator.new_rest(rest) def Skip(self, skip): r""" invisible rest/spacer rest (s or command \skip)""" if 'lyrics' in self.sims_and_seqs: self.mediator.new_lyrics_item(skip.token) else: self.mediator.new_rest(skip) def Scaler(self, scaler): r""" \times \tuplet \scaleDurations """ if scaler.token == '\\scaleDurations': ttype = "" fraction = (scaler.denominator, scaler.numerator) elif scaler.token == '\\times': ttype = "start" fraction = (scaler.denominator, scaler.numerator) elif scaler.token == '\\tuplet': ttype = "start" fraction = (scaler.numerator, scaler.denominator) nr = len(self.tuplet) + 1 self.tuplet.append({'set': False, 'fraction': fraction, 'ttype': ttype, 'length': scaler.length(), 'nr': nr}) if self.look_ahead(scaler, ly.music.items.Duration): self.tupl_span = True self.unset_tuplspan = True def Number(self, number): pass def Articulation(self, art): """An articulation, fingering, string number, or other symbol.""" self.mediator.new_articulation(art.token) def Postfix(self, postfix): pass def Beam(self, beam): pass def Slur(self, slur): """ Slur, '(' = start, ')' = stop. """ if slur.token == '(': self.slurcount += 1 self.slurnr = self.slurcount self.mediator.set_slur(self.slurnr, "start") elif slur.token == ')': self.mediator.set_slur(self.slurnr, "stop") self.slurcount -= 1 def PhrasingSlur(self, phrslur): r"""A \( or \).""" if phrslur.token == '\(': self.slurcount += 1 self.phrslurnr = self.slurcount self.mediator.set_slur(self.phrslurnr, "start") elif phrslur.token == '\)': self.mediator.set_slur(self.phrslurnr, "stop") self.slurcount -= 1 def Dynamic(self, dynamic): """Any dynamic symbol.""" self.mediator.new_dynamics(dynamic.token[1:]) def Grace(self, grace): self.grace_seq = True def TimeSignature(self, timeSign): self.mediator.new_time(timeSign.numerator(), timeSign.fraction(), self.numericTime) def Repeat(self, repeat): if repeat.specifier() == 'volta': self.mediator.new_repeat('forward') elif repeat.specifier() == 'tremolo': self.trem_rep = repeat.repeat_count() def Tremolo(self, tremolo): """A tremolo item ":".""" if self.look_ahead(tremolo, ly.music.items.Duration): self.tremolo = True else: self.mediator.set_tremolo() def With(self, cont_with): r"""A \with ... construct.""" self.with_contxt = cont_with.parent().context() def Set(self, cont_set): r"""A \set command.""" if isinstance(cont_set.value(), ly.music.items.Scheme): if cont_set.property() == 'tupletSpannerDuration': moment = cont_set.value().get_ly_make_moment() if moment: self.mediator.set_tuplspan_dur(fraction=moment) else: self.mediator.unset_tuplspan_dur() return val = cont_set.value().get_string() else: val = cont_set.value().value() if cont_set.context() in part_contexts: self.mediator.set_by_property(cont_set.property(), val) elif cont_set.context() in group_contexts: self.mediator.set_by_property(cont_set.property(), val, group=True) def Command(self, command): r""" \bar, \rest etc """ excls = ['\\major', '\\minor', '\\bar'] if command.token == '\\rest': self.mediator.note2rest() elif command.token == '\\numericTimeSignature': self.numericTime = True elif command.token == '\\defaultTimeSignature': self.numericTime = False elif command.token.find('voice') == 1: self.mediator.set_voicenr(command.token[1:], piano=self.piano_staff) elif command.token == '\\glissando': try: self.mediator.new_gliss(self.override_dict["Glissando.style"]) except KeyError: self.mediator.new_gliss() elif command.token == '\\startTrillSpan': self.mediator.new_trill_spanner() elif command.token == '\\stopTrillSpan': self.mediator.new_trill_spanner("stop") elif command.token == '\\ottava': self.ottava = True elif command.token == '\\default': if self.tupl_span: self.mediator.unset_tuplspan_dur() self.tupl_span = False else: if command.token not in excls: print("Unknown command:", command.token) def UserCommand(self, usercommand): """Music variables are substituted so this must be something else.""" if usercommand.name() == 'tupletSpan': self.tupl_span = True def String(self, string): prev = self.get_previous_node(string) if prev and prev.token == '\\bar': self.mediator.create_barline(string.value()) def LyricsTo(self, lyrics_to): r"""A \lyricsto expression. """ self.mediator.new_lyric_section('lyricsto'+lyrics_to.context_id(), lyrics_to.context_id()) self.sims_and_seqs.append('lyrics') def LyricText(self, lyrics_text): """A lyric text (word, markup or string), with a Duration.""" self.mediator.new_lyrics_text(lyrics_text.token) def LyricItem(self, lyrics_item): """Another lyric item (skip, extender, hyphen or tie).""" self.mediator.new_lyrics_item(lyrics_item.token) def NoteMode(self, notemode): r"""A \notemode or \notes expression.""" self.alt_mode = 'note' def ChordMode(self, chordmode): r"""A \chordmode or \chords expression.""" self.alt_mode = 'chord' def DrumMode(self, drummode): r"""A \drummode or \drums expression. If the shorthand form \drums is found, DrumStaff is implicit. """ if drummode.token == '\\drums': self.check_context('DrumStaff') self.alt_mode = 'drum' def FigureMode(self, figmode): r"""A \figuremode or \figures expression.""" self.alt_mode = 'figure' def LyricMode(self, lyricmode): r"""A \lyricmode, \lyrics or \addlyrics expression.""" self.alt_mode = 'lyric' def Override(self, override): r"""An \override command.""" self.override_key = '' def PathItem(self, item): r"""An item in the path of an \override or \revert command.""" self.override_key += item.token def Scheme(self, scheme): """A Scheme expression inside LilyPond.""" pass def SchemeItem(self, item): """Any scheme token.""" if self.ottava: self.mediator.new_ottava(item.token) self.ottava = False elif self.look_behind(item, ly.music.items.Override): self.override_dict[self.override_key] = item.token elif self.schm_assignm: self.mediator.set_by_property(self.schm_assignm, item.token) else: print("SchemeItem not implemented:", item.token) def SchemeQuote(self, quote): """A ' in scheme.""" pass def End(self, end): if isinstance(end.node, ly.music.items.Scaler): if self.unset_tuplspan: self.mediator.unset_tuplspan_dur() self.unset_tuplspan = False if end.node.token != '\\scaleDurations': self.mediator.change_tuplet_type(len(self.tuplet) - 1, "stop") self.tuplet.pop() self.fraction = None elif isinstance(end.node, ly.music.items.Grace): #Grace self.grace_seq = False elif end.node.token == '\\repeat': if end.node.specifier() == 'volta': self.mediator.new_repeat('backward') elif end.node.specifier() == 'tremolo': if self.look_ahead(end.node, ly.music.items.MusicList): self.mediator.set_tremolo(trem_type="stop") else: self.mediator.set_tremolo(trem_type="single") self.trem_rep = 0 elif isinstance(end.node, ly.music.items.Context): self.in_context = False if end.node.context() == 'Voice': self.mediator.check_voices() self.sims_and_seqs.pop() elif end.node.context() in group_contexts: self.mediator.close_group() elif end.node.context() in staff_contexts: if not self.piano_staff: self.mediator.check_part() elif end.node.context() in pno_contexts: self.mediator.check_voices() self.mediator.check_part() self.piano_staff = 0 self.mediator.set_voicenr(nr=1) elif end.node.context() == 'Devnull': self.mediator.check_voices() elif end.node.token == '<<': if self.voice_sep: self.mediator.check_voices_by_nr() self.mediator.revert_voicenr() self.voice_sep = False elif not self.piano_staff: self.mediator.check_simultan() if self.sims_and_seqs: self.sims_and_seqs.pop() elif end.node.token == '{': if self.sims_and_seqs: self.sims_and_seqs.pop() elif end.node.token == '<': #chord self.mediator.chord_end() elif end.node.token == '\\lyricsto': self.mediator.check_lyrics(end.node.context_id()) self.sims_and_seqs.pop() elif end.node.token == '\\with': self.with_contxt = None elif end.node.token == '\\drums': self.mediator.check_part() elif isinstance(end.node, ly.music.items.Relative): self.relative = False self.rel_pitch_isset = False else: # print("end:", end.node.token) pass ## # Additional node manipulation ## def get_previous_node(self, node): """ Returns the nodes previous node or false if the node is first in its branch. """ parent = node.parent() i = parent.index(node) if i > 0: return parent[i-1] else: return False def simple_node_gen(self, node): """Unlike iter_score are the subnodes yielded without substitution.""" for n in node: yield n for s in self.simple_node_gen(n): yield s def iter_header(self, tree): """Iter only over header nodes.""" for t in tree: if isinstance(t, ly.music.items.Header): return self.simple_node_gen(t) def get_score(self, node): """ Returns (first) Score node or false if no Score is found. """ for n in node: if isinstance(n, ly.music.items.Score) or isinstance(n, ly.music.items.Book): return n return False def iter_score(self, scorenode, doc): r""" Iter over score. Similarly to items.Document.iter_music user commands are substituted. Furthermore \repeat unfold expressions are unfolded. """ for s in scorenode: if isinstance(s, ly.music.items.Repeat) and s.specifier() == 'unfold': for u in self.unfold_repeat(s, s.repeat_count(), doc): yield u else: n = doc.substitute_for_node(s) or s yield n for c in self.iter_score(n, doc): yield c if isinstance(s, ly.music.items.Container): yield End(s) def unfold_repeat(self, repeat_node, repeat_count, doc): r""" Iter over node which represent a \repeat unfold expression and do the unfolding directly. """ for r in range(repeat_count): for n in repeat_node: for c in self.iter_score(n, doc): yield c def find_score_sub(self, doc): """Find substitute for scorenode. Takes first music node that isn't an assignment.""" for n in doc: if not isinstance(n, ly.music.items.Assignment): if isinstance(n, ly.music.items.Music): return self.iter_score(n, doc) def look_ahead(self, node, find_node): """Looks ahead in a container node and returns True if the search is successful.""" for n in node: if isinstance(n, find_node): return True return False def look_behind(self, node, find_node): """Looks behind on the parent node(s) and returns True if the search is successful.""" parent = node.parent() if parent: if isinstance(parent, find_node): ret = True else: ret = self.look_behind(parent, find_node) return ret else: return False ## # Other functions ## def gen_med_caller(self, func_name, *args): """Call any function in the mediator object.""" func_call = getattr(self.mediator, func_name) func_call(*args) python-ly-0.9.3/ly/musicxml/__init__.py0000644000175000017500000000306112525203625021230 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ MusicXML functionality. This subpackage is created to convert LilyPond text to MusicXML with the help of the tree structure created by ly.music. But it is constructed in such a way that you can use some of the submodules for generic MusicXML creation and manipulation. """ from __future__ import unicode_literals def writer(): """Convert LilyPond text to MusicXML Example:: import ly.musicxml e = ly.musicxml.writer() e.parse_text(lilypond_text) xml = e.musicxml() xml.write(filename) # or: xml.tostring() # xml.tree is the ElementTree xml tree. """ from . import lymus2musxml return lymus2musxml.ParseSource() python-ly-0.9.3/ly/musicxml/ly2xml_mediator.py0000664000175000017500000010232712635547305022623 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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 information of the parsed source is organised into an object structure with help of the classes in ly.musicxml.xml_objs. """ from __future__ import unicode_literals from __future__ import print_function from __future__ import division from fractions import Fraction import ly.duration import ly.pitch from . import xml_objs class Mediator(): """Help class that acts as mediator between the ly source parser and the XML creating modules.""" def __init__(self): """ create global lists """ self.score = xml_objs.Score() self.sections = [] """ default and initial values """ self.insert_into = None self.current_note = None self.current_lynote = None self.current_is_rest = False self.action_onnext = [] self.divisions = 1 self.dur_token = "4" self.dur_tokens = () self.dots = 0 self.tied = False self.voice = 1 self.staff = 0 self.part = None self.group = None self.group_num = 0 self.current_chord = [] self.q_chord = [] self.prev_pitch = None self.prev_chord_pitch = None self.store_voicenr = 0 self.staff_id_dict = {} self.store_unset_staff = False self.staff_unset_notes = {} self.lyric_sections = {} self.lyric = None self.lyric_syll = False self.lyric_nr = 1 self.ongoing_wedge = False self.octdiff = 0 self.prev_tremolo = 8 self.tupl_dur = 0 self.tupl_sum = 0 def new_header_assignment(self, name, value): """Distributing header information.""" creators = ['composer', 'arranger', 'poet', 'lyricist'] if name == 'title': self.score.title = value elif name == 'copyright': self.score.rights = value elif name in creators: self.score.creators[name] = value else: self.score.info[name] = value def new_section(self, name, sim=False): name = self.check_name(name) section = xml_objs.ScoreSection(name, sim) self.insert_into = section self.sections.append(section) self.bar = None def new_snippet(self, name): name = self.check_name(name) snippet = xml_objs.Snippet(name, self.insert_into) self.insert_into = snippet self.sections.append(snippet) self.bar = None def new_lyric_section(self, name, voice_id): name = self.check_name(name) lyrics = xml_objs.LyricsSection(name, voice_id) self.insert_into = lyrics self.lyric_sections[name] = lyrics def check_name(self, name, nr=1): n = self.get_var_byname(name) if n: name = name+str(nr) name = self.check_name(name, nr+1) return name def get_var_byname(self, name): for n in self.sections: if n.name == name: return n def new_group(self): parent = self.group self.group_num += 1 self.group = xml_objs.ScorePartGroup(self.group_num, "bracket") if parent: #nested group self.group.parent = parent parent.partlist.append(self.group) else: self.score.partlist.append(self.group) def close_group(self): if self.group.parent: self.group = self.group.parent else: self.group = None def change_group_bracket(self, system_start): self.group.set_bracket(get_group_symbol(system_start)) def new_part(self, pid=None, to_part=None, piano=False): if piano: self.part = xml_objs.ScorePart(2, pid, to_part) else: self.part = xml_objs.ScorePart(part_id=pid, to_part=to_part) if not to_part: if self.group: self.group.partlist.append(self.part) else: self.score.partlist.append(self.part) self.insert_into = self.part self.bar = None def part_not_empty(self): return self.part and self.part.barlist def get_part_by_id(self, pid, partholder=None): if not partholder: partholder = self.score ret = False for part in partholder.partlist: if isinstance(part, xml_objs.ScorePartGroup): ret = self.get_part_by_id(pid, part) else: if part.part_id == pid: ret = part return ret def set_voicenr(self, command=None, add=False, nr=0, piano=0): if add: if not self.store_voicenr: self.store_voicenr = self.voice self.voice += 1 elif nr: self.voice = nr else: self.voice = get_voice(command) if piano>2: self.voice += piano+1 def revert_voicenr(self): self.voice = self.store_voicenr self.store_voicenr = 0 def set_staffnr(self, staffnr, staff_id=None): self.store_unset_staff = False if staffnr: self.staff = staffnr elif staff_id in self.staff_id_dict: self.staff = self.staff_id_dict[staff_id] elif staff_id: self.store_unset_staff = True self.staff = staff_id def add_staff_id(self, staff_id): self.store_unset_staff = False if staff_id: if staff_id in self.staff_id_dict: self.staff = self.staff_id_dict[staff_id] else: self.staff_id_dict[staff_id] = self.staff if staff_id in self.staff_unset_notes: for n in self.staff_unset_notes[staff_id]: n.staff = self.staff def add_snippet(self, snippet_name): """ Adds snippet to previous barlist. A snippet can be shorter than a full bar, so this can also mean continuing a previous bar.""" def continue_barlist(insert_into): self.insert_into = insert_into if insert_into.barlist: self.bar = insert_into.barlist[-1] else: self.new_bar(False) snippet = self.get_var_byname(snippet_name) continue_barlist(snippet.merge_barlist) for bb in snippet.barlist: for b in bb.obj_list: self.bar.add(b) if bb.list_full: self.new_bar() def check_voices(self): """ Checks active sections. The two latest created are merged. Also checks for empty sections. """ if len(self.sections)>2: if not self.sections[-2].barlist: self.sections.pop(-2) self.check_voices() elif not self.sections[-1].barlist: self.sections.pop() self.check_voices() else: self.sections[-2].merge_voice(self.sections[-1]) self.sections.pop() def check_voices_by_nr(self): """ Used for snippets. Merges all active snippets created after the stored voice number.""" sect_len = len(self.sections) if sect_len>2: if self.voice>1: for n in range(self.store_voicenr, self.voice): self.check_voices() if isinstance(self.sections[-1], xml_objs.Snippet): self.add_snippet(self.sections[-1].name) self.sections.pop() else: print("WARNING: problem adding snippet!") def check_lyrics(self, voice_id): """Check the finished lyrics section and merge it into the referenced voice.""" if self.lyric[1] == 'middle': self.lyric[1] = 'end' lyrics_section = self.lyric_sections['lyricsto'+voice_id] voice_section = self.get_var_byname(lyrics_section.voice_id) if voice_section: voice_section.merge_lyrics(lyrics_section) else: print("Warning can't merge in lyrics!", voice_section) def check_part(self): """Adds the latest active section to the part.""" if len(self.sections)>1: if self.score.is_empty(): self.new_part() if self.sections[-1].simultan: self.part.merge_voice(self.sections[-1]) else: self.part.barlist.extend(self.sections[-1].barlist) self.sections.pop() if self.part and self.part.to_part: self.part.merge_part_to_part() self.part = None def check_simultan(self): """Check done after simultanoues (<< >>) section.""" if self.sections[-1].simultan: if self.part: self.part.merge_voice(self.sections[-1]) elif len(self.sections)>1: self.sections[-2].merge_voice(self.sections[-1]) self.sections.pop() def check_score(self): """ Check score If no part were created, place first variable (fallback) as part. More checks? """ if self.score.is_empty(): self.new_part() self.part.barlist.extend(self.get_first_var()) def get_first_var(self): if self.sections: return self.sections[0].barlist def new_bar(self, fill_prev=True): if self.bar and fill_prev: self.bar.list_full = True self.current_attr = xml_objs.BarAttr() self.bar = xml_objs.Bar() self.bar.obj_list = [self.current_attr] self.insert_into.barlist.append(self.bar) def add_to_bar(self, obj): if self.bar is None: self.new_bar() self.bar.add(obj) def create_barline(self, bl): barline = xml_objs.BarAttr() barline.set_barline(bl) self.bar.add(barline) self.new_bar() def new_repeat(self, rep): barline = xml_objs.BarAttr() barline.set_barline(rep) barline.repeat = rep if self.bar is None: self.new_bar() self.bar.add(barline) def new_key(self, key_name, mode): if self.bar is None: self.new_bar() if self.bar.has_music(): new_bar_attr = xml_objs.BarAttr() new_bar_attr.set_key(get_fifths(key_name, mode), mode) self.add_to_bar(new_bar_attr) else: self.current_attr.set_key(get_fifths(key_name, mode), mode) def new_time(self, num, den, numeric=False): if self.bar is None: self.new_bar() self.current_attr.set_time([num, den.denominator], numeric) def new_clef(self, clefname): self.clef = clefname2clef(clefname) if self.bar is None: self.new_bar() if self.bar.has_music(): new_bar_attr = xml_objs.BarAttr() new_bar_attr.set_clef(self.clef) self.add_to_bar(new_bar_attr) else: if self.staff: self.current_attr.multiclef.append((self.clef, self.staff)) else: self.current_attr.set_clef(self.clef) def set_relative(self, note): self.prev_pitch = note.pitch def new_note(self, note, rel=False, is_unpitched=False): self.current_is_rest = False self.clear_chord() if is_unpitched: self.current_note = self.create_unpitched(note) self.check_current_note(is_unpitched=True) else: self.current_note = self.create_barnote_from_note(note) self.current_lynote = note self.check_current_note(rel) self.do_action_onnext(self.current_note) self.action_onnext = [] def new_iso_dura(self, note, rel=False, is_unpitched=False): """ Isolated durations in music sequences. An isolated duration in LilyPond is rendered as a normal note but the pitch information is missing and has to be filled in by some other means, usually by the previous pitch. (RhythmicStaff is an exception since it ignores specified pitches anyway). """ if self.current_chord: self.copy_prev_chord(note.duration) else: if not is_unpitched: note.pitch = self.current_lynote.pitch self.new_note(note, rel, is_unpitched) def create_unpitched(self, unpitched): """Create a xml_objs.Unpitched from ly.music.items.Unpitched.""" dura = unpitched.duration return xml_objs.Unpitched(dura) def create_barnote_from_note(self, note): """Create a xml_objs.BarNote from ly.music.items.Note.""" p = getNoteName(note.pitch.note) alt = get_xml_alter(note.pitch.alter) try: acc = note.accidental_token except AttributeError: acc = "" dura = note.duration return xml_objs.BarNote(p, alt, acc, dura, self.voice) def copy_barnote_basics(self, bar_note): """Create a copy of a xml_objs.BarNote.""" p = bar_note.base_note alt = bar_note.alter acc = bar_note.accidental_token dura = bar_note.duration voc = bar_note.voice copy = xml_objs.BarNote(p, alt, acc, dura, voc) copy.octave = bar_note.octave copy.chord = bar_note.chord return copy def new_duration_token(self, token, tokens): self.dur_token = token self.dur_tokens = tokens self.check_duration(self.current_is_rest) def check_current_note(self, rel=False, rest=False, is_unpitched=False): """ Perform checks common for all new notes and rests. """ if not rest and not is_unpitched: self.set_octave(rel) if not rest: if self.tied: self.current_note.set_tie('stop') self.tied = False self.check_duration(rest) self.check_divs() if self.staff: self.current_note.set_staff(self.staff) if self.store_unset_staff: if self.staff in self.staff_unset_notes: self.staff_unset_notes[self.staff].append(self.current_note) else: self.staff_unset_notes[self.staff] = [self.current_note] self.add_to_bar(self.current_note) def set_octave(self, relative): """Set octave by getting the octave of an absolute note + 3.""" p = self.current_lynote.pitch.copy() if relative: p.makeAbsolute(self.prev_pitch) self.prev_pitch = p self.current_note.set_octave(p.octave + 3) def do_action_onnext(self, note): """Perform the stored action on the next note.""" for action in self.action_onnext: func_call = getattr(self, action[0]) func_call(note, *action[1]) def check_duration(self, rest): """Check the duration for the current note.""" dots, rs = self.duration_from_tokens(self.dur_tokens) if rest and rs: # special case of multibar rest if not self.current_note.show_type or self.current_note.skip: bs = self.current_note.duration if rs == bs[1]: self.current_note.duration = (bs[0], 1) self.current_note.dot = 0 self.scale_rest(rs) return self.current_note.dot = dots self.dots = dots self.current_note.set_durtype(durval2type(self.dur_token)) if self.current_chord: for c in self.current_chord: c.set_durtype(durval2type(self.dur_token)) def new_chord(self, note, duration, rel=False): if not self.current_chord: self.new_chordbase(note, duration, rel) self.current_chord.append(self.current_note) else: self.current_chord.append(self.new_chordnote(note, rel)) self.do_action_onnext(self.current_chord[-1]) def new_chordbase(self, note, duration, rel=False): self.current_note = self.create_barnote_from_note(note) self.current_note.set_duration(duration) self.current_lynote = note self.check_current_note(rel) def new_chordnote(self, note, rel): chord_note = self.create_barnote_from_note(note) chord_note.set_duration(self.current_note.duration) chord_note.set_durtype(durval2type(self.dur_token)) chord_note.dots = self.dots chord_note.tie = self.current_note.tie if not self.prev_chord_pitch: self.prev_chord_pitch = self.prev_pitch p = note.pitch.copy() if(rel): p.makeAbsolute(self.prev_chord_pitch) chord_note.set_octave(p.octave + 3) self.prev_chord_pitch = p chord_note.chord = True self.bar.add(chord_note) return chord_note def copy_prev_chord(self, duration): if self.current_chord: prev_chord = self.current_chord self.clear_chord() else: prev_chord = self.q_chord for i, pc in enumerate(prev_chord): cn = self.copy_barnote_basics(pc) cn.set_duration(duration) cn.set_durtype(durval2type(self.dur_token)) if i == 0: self.current_note = cn self.current_chord.append(cn) if self.tied: cn.set_tie('stop') self.bar.add(cn) self.tied = False def clear_chord(self): self.q_chord = self.current_chord self.current_chord = [] self.prev_chord_pitch = None def chord_end(self): """Actions when chord is parsed.""" self.action_onnext = [] def new_rest(self, rest): self.current_is_rest = True self.clear_chord() rtype = rest.token dur = rest.duration if rtype == 'r': self.current_note = xml_objs.BarRest(dur, self.voice) elif rtype == 'R': self.current_note = xml_objs.BarRest(dur, self.voice, show_type=False) elif rtype == 's' or rtype == '\\skip': self.current_note = xml_objs.BarRest(dur, self.voice, skip=True) self.check_current_note(rest=True) def note2rest(self): """Note used as rest position transformed to rest.""" dur = self.current_note.duration voice = self.current_note.voice pos = [self.current_note.base_note, self.current_note.octave] self.current_note = xml_objs.BarRest(dur, voice, pos=pos) self.check_duration(rest=True) self.bar.obj_list.pop() self.bar.add(self.current_note) def scale_rest(self, multp): """ create multiple whole bar rests """ dur = self.current_note.duration voc = self.current_note.voice st = self.current_note.show_type sk = self.current_note.skip for i in range(1, int(multp)): self.new_bar() rest_copy = xml_objs.BarRest(dur, voice=voc, show_type=st, skip=sk) self.add_to_bar(rest_copy) def change_to_tuplet(self, tfraction, ttype, nr, length=None): """Change the current note into a tuplet note.""" tuplscaling = Fraction(tfraction[0], tfraction[1]) if self.tupl_dur: if self.tupl_sum == 0: ttype = "start" base, scaling = self.current_lynote.duration self.tupl_sum += (1 / tuplscaling) * base * scaling if self.tupl_sum == self.tupl_dur: ttype = "stop" self.tupl_sum = 0 if length: acttype = normtype = durval2type(self.calc_tupl_den(tfraction, length)) self.current_note.set_tuplet(tfraction, ttype, nr, acttype, normtype) else: self.current_note.set_tuplet(tfraction, ttype, nr) def change_tuplet_type(self, index, newtype): self.current_note.tuplet[index].ttype = newtype def set_tuplspan_dur(self, token=None, tokens=None, fraction=None): """ Catch duration set by the tupletSpannerDuration property. Set the fraction directly or calculate it from tokens. """ if fraction: self.tupl_dur = fraction else: base, scaling = ly.duration.base_scaling((token,) + tokens) self.tupl_dur = base * scaling def unset_tuplspan_dur(self): """Reset tuplet duration sum and tuplet spanner duration.""" self.tupl_sum = 0 self.tupl_dur = 0 def calc_tupl_den(self, tfraction, length): """Calculate the tuplet denominator from fraction and duration of tuplet.""" return tfraction[1] / length def tie_to_next(self): tie_type = 'start' self.tied = True self.current_note.set_tie(tie_type) def set_slur(self, nr, slur_type): """ Set the slur start or stop for the current note. """ self.current_note.set_slur(nr, slur_type) def new_articulation(self, art_token): """ An articulation, fingering, string number, or other symbol. Grouped as articulations, ornaments, technical and others. """ if isinstance(art_token, ly.lex.lilypond.Fingering): self.current_note.add_fingering(art_token) else: ret = artic_token2xml_name(art_token) if ret == 'ornament': self.current_note.add_ornament(art_token[1:]) elif ret == 'other': self.current_note.add_other_notation(art_token[1:]) elif ret: self.current_note.add_articulation(ret) def new_dynamics(self, dynamics): hairpins = {'<': 'crescendo', '>': 'diminuendo'} if dynamics == '!': self.current_note.set_dynamics(wedge='stop') self.ongoing_wedge = False elif dynamics in hairpins: self.current_note.set_dynamics(wedge=hairpins[dynamics]) self.ongoing_wedge = True elif self.ongoing_wedge: self.current_note.set_dynamics(wedge='stop') self.current_note.set_dynamics(mark=dynamics) self.ongoing_wedge = False else: self.current_note.set_dynamics(mark=dynamics) def new_grace(self, slash=0): self.current_note.set_grace(slash) def new_chord_grace(self, slash=0): self.current_chord[-1].set_grace(slash) def new_gliss(self, line=None): if line: line = get_line_style(line) if self.current_chord: for n, c in enumerate(self.current_chord): c.set_gliss(line, nr=n+1) else: self.current_note.set_gliss(line) self.action_onnext.append(("end_gliss", line)) def end_gliss(self, note, line): if self.current_chord: n = len(self.current_chord) else: n = 1 note.set_gliss(line, endtype="stop", nr=n) def set_tremolo(self, trem_type='single', duration=0, repeats=0): if self.current_note.tremolo[1]: #tremolo already set self.current_note.set_tremolo(trem_type) else: if repeats: duration = int(self.dur_token) bs, durtype = calc_trem_dur(repeats, self.current_note.duration, duration) self.current_note.duration = bs self.current_note.type = durtype elif not duration: duration = self.prev_tremolo else: self.prev_tremolo = duration self.current_note.set_tremolo(trem_type, duration) def new_trill_spanner(self, end=None): if not end: self.current_note.add_ornament('trill') end = "start" self.current_note.add_adv_ornament('wavy-line', end) def new_ottava(self, octdiff): octdiff = int(octdiff) if self.octdiff == octdiff: return if self.octdiff: if self.octdiff < 0: plac = "below" else: plac = "above" size = abs(self.octdiff) * 7 + 1 self.current_note.set_oct_shift(plac, "stop", size) if octdiff: if octdiff < 0: plac = "below" octdir = "up" else: plac = "above" octdir = "down" size = abs(octdiff) * 7 + 1 self.action_onnext.append(("set_ottava", (plac, octdir, size))) self.octdiff = octdiff def set_ottava(self, note, plac, octdir, size): note.set_oct_shift(plac, octdir, size) def new_tempo(self, unit, dur_tokens, tempo, string): dots, rs = self.duration_from_tokens(dur_tokens) if tempo: beats = tempo[0] else: beats = 0 try: text = string.value() except AttributeError: text = None tempo = xml_objs.BarAttr() tempo.set_tempo(unit, durval2type(unit), beats, dots, text) if self.bar is None: self.new_bar() self.bar.add(tempo) def set_by_property(self, prprty, value, group=False): """Generic setter for different properties.""" if prprty == 'instrumentName': if group: self.set_groupname(value) else: self.set_partname(value) elif prprty == 'shortInstrumentName': if group: self.set_groupabbr(value) else: self.set_partabbr(value) elif prprty == 'midiInstrument': self.set_partmidi(value) elif prprty == 'stanza': self.new_lyric_nr(value) elif prprty == 'systemStartDelimiter': self.change_group_bracket(value) def set_partname(self, name): if self.score.is_empty(): self.new_part() self.part.name = name def set_partabbr(self, abbr): if self.score.is_empty(): self.new_part() self.part.abbr = abbr def set_groupname(self, name): if self.group: self.group.name = name def set_groupabbr(self, abbr): if self.group: self.group.abbr = abbr def set_partmidi(self, midi): if self.score.is_empty(): self.new_part() self.part.midi = midi def new_lyric_nr(self, num): self.lyric_nr = num def new_lyrics_text(self, txt): if self.lyric: if self.lyric_syll: if self.lyric[1] in ['begin', 'middle']: self.lyric = [txt, 'middle', self.lyric_nr] else: if self.lyric[1] in ['begin', 'middle']: self.lyric[1] = 'end' self.lyric = [txt, 'single', self.lyric_nr] else: self.lyric = [txt, 'single', self.lyric_nr] self.insert_into.barlist.append(self.lyric) self.lyric_syll = False def new_lyrics_item(self, item): if item == '--': if self.lyric: if self.lyric[1] == 'single': self.lyric[1] = 'begin' self.lyric_syll = True elif item == '__': self.lyric.append("extend") elif item == '\\skip': self.insert_into.barlist.append("skip") def duration_from_tokens(self, tokens): """Calculate dots and multibar rests from tokens.""" dots = 0 rs = 0 for t in tokens: if t == '.': dots += 1 elif '*' in t and '/' not in t: rs = int(t[1:]) return (dots, rs) def check_divs(self): """ The new duration is checked against current divisions """ base = self.current_note.duration[0] scaling = self.current_note.duration[1] divs = self.divisions tupl = self.current_note.tuplet if not tupl: a = 4 if base: b = 1/base else: b = 1 print("Warning problem checking duration!") else: num = 1 den = 1 for t in tupl: num *= t.fraction[0] den *= t.fraction[1] a = 4*den b = (1/base)*num c = a * divs * scaling predur, mod = divmod(c, b) if mod > 0: mult = get_mult(a, b) self.divisions = divs*mult ## # Translation functions ## def getNoteName(index): noteNames = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] return noteNames[index] def get_xml_alter(alter): """ Convert alter to the specified format, i e int if it's int and float otherwise. Also multiply with 2.""" alter *= 2 if float(alter).is_integer(): return alter else: return float(alter) def durval2type(durval): """Convert LilyPond duration to MusicXML duration type.""" xml_types = [ "maxima", "long", "breve", "whole", "half", "quarter", "eighth", "16th", "32nd", "64th", "128th", "256th", "512th", "1024th", "2048th" ] # Note: 2048 is supported by ly but not by MusicXML! try: type_index = ly.duration.durations.index(str(durval)) except ValueError: type_index = 5 return xml_types[type_index] def get_fifths(key, mode): fifths = 0 sharpkeys = ['c', 'g', 'd', 'a', 'e', 'b', 'fis', 'cis', 'gis', 'dis', 'ais', 'eis', 'bis', 'fisis', 'cisis'] flatkeys = ['c', 'f', 'bes', 'es', 'as', 'des', 'ges', 'ces', 'fes', 'beses', 'eses', 'ases'] if key in sharpkeys: fifths = sharpkeys.index(key) elif key in flatkeys: fifths = -flatkeys.index(key) if mode=='minor': return fifths-3 elif mode=='major': return fifths def clefname2clef(clefname): """ To add a clef look up the clef name in LilyPond and the corresponding definition in musicXML. Add it to the python dictionary below. """ clef_dict = { "treble": ('G', 2, 0), "violin": ('G', 2, 0), "G": ('G', 2, 0), "bass": ('F', 4, 0), "F": ('F', 4, 0), "alto": ('C', 3, 0), "C": ('C', 3, 0), "tenor": ('C', 4, 0), "treble_8": ('G', 2, -1), "treble_15": ('G', 2, -2), "bass_8": ('F', 4, -1), "bass_15": ('F', 4, -2), "treble^8": ('G', 2, 1), "treble^15": ('G', 2, 2), "bass^8": ('F', 4, 1), "bass^15": ('F', 4, 2), "percussion": ('percussion', 0, 0), "tab": ('TAB', 5, 0), "soprano": ('C', 1, 0), "mezzosoprano": ('C', 2, 0), "baritone": ('C', 5, 0), "varbaritone": ('F', 3, 0), "baritonevarF": ('F', 3, 0), "french": ('G', 1, 0), "subbass": ('F', 5, 0), # From here on the clefs will end up with wrong symbols "GG": ('G', 2, -1), "tenorG": ('G', 2, -1), "varC": ('C', 3, 0), "altovarC": ('C', 3, 0), "tenorvarC": ('C', 4, 0), "baritonevarC": ('C', 5, 0), } try: clef = clef_dict[clefname] except KeyError: clef = 0 return clef def get_mult(num, den): simple = Fraction(num, den) return simple.denominator def get_voice(c): voices = ["voiceOne", "voiceTwo", "voiceThree", "voiceFour"] return voices.index(c)+1 def artic_token2xml_name(art_token): """ From Articulations in ly.music.items. Grouped as articulations, ornaments and others. To add an articulation look up the name or abbreviation in LilyPond and the corresponding node name in musicXML. Add it to the python dictionary below. """ artic_dict = { ".": "staccato", "-": "tenuto", ">": "accent", "_": "detached-legato", "!": "staccatissimo", "\\staccatissimo": "staccatissimo" } ornaments = ['\\trill', '\\prall', '\\mordent', '\\turn'] others = ['\\fermata'] try: return artic_dict[art_token] except KeyError: if art_token in ornaments: return "ornament" elif art_token in others: return "other" else: return False def calc_trem_dur(repeats, base_scaling, duration): """ Calculate tremolo duration from number of repeats and initial duration. """ base = base_scaling[0] scale = base_scaling[1] new_base = base * repeats if repeats > duration: import ly.duration trem_length = ly.duration.tostring(int((repeats // duration) * -0.5)) else: trem_length = str(duration // repeats) new_type = xml_objs.durval2type(trem_length) return (new_base, scale), new_type def get_line_style(style): style_dict = { "dashed-line": "dashed", "dotted-line": "dotted", "trill": "wavy", "zigzag": "wavy" } try: return style_dict[style] except KeyError: return False def get_group_symbol(lily_sys_start): symbol_dict = { "SystemStartBrace": "brace", "SystemStartSquare": "square" } try: return symbol_dict[lily_sys_start] except KeyError: return False python-ly-0.9.3/ly/musicxml/xml_objs.py0000664000175000017500000006025512635547305021330 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Classes that holds information about a musical score, suitable for converting to musicXML. When the score structure is built, it can easily be used to create a musicXML. Example:: from ly.musicxml import create_musicxml, xml_objs musxml = create_musicxml.CreateMusicXML() score = xml_objs.Score() part = xml_objs.ScorePart() score.partlist.append(part) bar = xml_objs.Bar() part.barlist.append(bar) ba = xml_objs.BarAttr() ba.set_time([4,4]) bar.obj_list.append(ba) c = xml_objs.BarNote('c', 0, 0, (1,1)) c.set_octave(4) c.set_durtype(1) bar.obj_list.append(c) xml_objs.IterateXmlObjs(score, musxml, 1) xml = musxml.musicxml() xml.write(filename) """ from __future__ import unicode_literals from __future__ import print_function from fractions import Fraction class IterateXmlObjs(): """ A ly.musicxml.xml_objs.Score object is iterated and the Music XML node tree is constructed. """ def __init__(self, score, musxml, div): """Create the basic score information, and initiate the iteration of the parts.""" # score.debug_score([]) self.musxml = musxml self.divisions = div if score.title: self.musxml.create_title(score.title) for ctag in score.creators: self.musxml.add_creator(ctag, score.creators[ctag]) for itag in score.info: self.musxml.create_score_info(itag, score.info[itag]) if score.rights: self.musxml.add_rights(score.rights) for p in score.partlist: if isinstance(p, ScorePart): self.iterate_part(p) elif isinstance(p, ScorePartGroup): self.iterate_partgroup(p) def iterate_partgroup(self, group): """Loop through a group, recursively if nested.""" self.musxml.create_partgroup( 'start', group.num, group.name, group.abbr, group.bracket) for p in group.partlist: if isinstance(p, ScorePart): self.iterate_part(p) elif isinstance(p, ScorePartGroup): self.iterate_partgroup(p) self.musxml.create_partgroup('stop', group.num) def iterate_part(self, part): """The part is iterated.""" if part.barlist: part.set_first_bar(self.divisions) self.musxml.create_part(part.name, part.abbr, part.midi) for bar in part.barlist: self.iterate_bar(bar) else: print("Warning: empty part:", part.name) def iterate_bar(self, bar): """The objects in the bar are output to the xml-file.""" self.musxml.create_measure() for obj in bar.obj_list: if isinstance(obj, BarAttr): self.new_xml_bar_attr(obj) elif isinstance(obj, BarMus): self.before_note(obj) if isinstance(obj, BarNote): self.new_xml_note(obj) elif isinstance(obj, BarRest): self.new_xml_rest(obj) self.gener_xml_mus(obj) self.after_note(obj) elif isinstance(obj, BarBackup): divdur = self.count_duration(obj.duration, self.divisions) self.musxml.add_backup(divdur) def new_xml_bar_attr(self, obj): """Create bar attribute xml-nodes.""" if obj.has_attr(): self.musxml.new_bar_attr(obj.clef, obj.time, obj.key, obj.mode, obj.divs) if obj.repeat: self.musxml.add_barline(obj.barline, obj.repeat) elif obj.barline: self.musxml.add_barline(obj.barline) if obj.staves: self.musxml.add_staves(obj.staves) if obj.multiclef: for mc in obj.multiclef: self.musxml.add_clef(sign=mc[0][0], line=mc[0][1], nr=mc[1], oct_ch=mc[0][2]) if obj.tempo: self.musxml.create_tempo(obj.tempo.text, obj.tempo.metr, obj.tempo.midi, obj.tempo.dots) def before_note(self, obj): """Xml-nodes before note.""" for d in obj.dynamic: if d.before: if d.is_mark: self.musxml.add_dynamic_mark(d.sign) else: self.musxml.add_dynamic_wedge(d.sign) if obj.oct_shift and not obj.oct_shift.octdir == 'stop': self.musxml.add_octave_shift(obj.oct_shift.plac, obj.oct_shift.octdir, obj.oct_shift.size) def after_note(self, obj): """Xml-nodes after note.""" for d in obj.dynamic: if not d.before: if d.is_mark: self.musxml.add_dynamic_mark(d.sign) else: self.musxml.add_dynamic_wedge(d.sign) if obj.oct_shift and obj.oct_shift.octdir == 'stop': self.musxml.add_octave_shift(obj.oct_shift.plac, obj.oct_shift.octdir, obj.oct_shift.size) def gener_xml_mus(self, obj): """Nodes generic for both notes and rests.""" if obj.tuplet: for t in obj.tuplet: self.musxml.tuplet_note(t.fraction, obj.duration, t.ttype, t.nr, self.divisions, t.acttype, t.normtype) if obj.staff and not obj.skip: self.musxml.add_staff(obj.staff) if obj.other_notation: self.musxml.add_named_notation(obj.other_notation) def new_xml_note(self, obj): """Create note specific xml-nodes.""" divdur = self.count_duration(obj.duration, self.divisions) if isinstance(obj, Unpitched): self.musxml.new_unpitched_note(obj.base_note, obj.octave, obj.type, divdur, obj.voice, obj.dot, obj.chord, obj.grace) else: self.musxml.new_note(obj.base_note, obj.octave, obj.type, divdur, obj.alter, obj.accidental_token, obj.voice, obj.dot, obj.chord, obj.grace) for t in obj.tie: self.musxml.tie_note(t) for s in obj.slur: self.musxml.add_slur(s.nr, s.slurtype) for a in obj.artic: self.musxml.new_articulation(a) if obj.ornament: self.musxml.new_simple_ornament(obj.ornament) if obj.adv_ornament: self.musxml.new_adv_ornament(obj.adv_ornament[0], obj.adv_ornament[1]) if obj.tremolo[1]: self.musxml.add_tremolo(obj.tremolo[0], obj.tremolo[1]) if obj.gliss: self.musxml.add_gliss(obj.gliss[0], obj.gliss[1], obj.gliss[2]) if obj.fingering: self.musxml.add_fingering(obj.fingering) if obj.lyric: for l in obj.lyric: try: self.musxml.add_lyric(l[0], l[1], l[2], l[3]) except IndexError: self.musxml.add_lyric(l[0], l[1], l[2]) def new_xml_rest(self, obj): """Create rest specific xml-nodes.""" divdur = self.count_duration(obj.duration, self.divisions) if obj.skip: self.musxml.add_skip(divdur) else: self.musxml.new_rest(divdur, obj.type, obj.pos, obj.dot, obj.voice) def count_duration(self, base_scaling, divs): base = base_scaling[0] scaling = base_scaling[1] duration = divs*4*base duration = duration * scaling return int(duration) class Score(): """Object that keep track of a whole score.""" def __init__(self): self.partlist = [] self.title = None self.creators = {} self.info = {} self.rights = None def is_empty(self): """Check if score is empty.""" if self.partlist: return False else: return True def debug_score(self, attr=[]): """ Loop through score and print all elements for debugging purposes. Additionally print element attributes by adding them to the argument 'attr' list. """ ind = " " def debug_part(p): print("Score part:"+p.name) for n, b in enumerate(p.barlist): print(ind+"Bar nr: "+str(n+1)) for obj in b.obj_list: print(ind+ind+repr(obj)) for a in attr: try: print(ind+ind+ind+a+':'+repr(getattr(obj, a))) except AttributeError: pass def debug_group(g): if hasattr(g, 'barlist'): debug_part(g) else: print("Score group:"+g.name) for pg in g.partlist: debug_group(pg) for i in self.partlist: debug_group(i) class ScorePartGroup(): """Object to keep track of part group.""" def __init__(self, num, bracket): self.bracket = bracket self.partlist = [] self.name = '' self.abbr = '' self.parent = None self.num = num def set_bracket(self, bracket): self.bracket = bracket class ScoreSection(): """ object to keep track of music section """ def __init__(self, name, sim=False): self.name = name self.barlist = [] self.simultan = sim def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.name) def merge_voice(self, voice): """Merge in other ScoreSection.""" for org_v, add_v in zip(self.barlist, voice.barlist): org_v.inject_voice(add_v) bl_len = len(self.barlist) if len(voice.barlist) > bl_len: self.barlist += voice.barlist[bl_len:] def merge_lyrics(self, lyrics): """Merge in lyrics in music section.""" i = 0 ext = False for bar in self.barlist: for obj in bar.obj_list: if isinstance(obj, BarNote): if ext: if obj.slur: ext = False else: try: l = lyrics.barlist[i] except IndexError: break if l != 'skip': try: if l[3] == "extend" and obj.slur: ext = True except IndexError: pass obj.add_lyric(l) i += 1 class Snippet(ScoreSection): """ Short section intended to be merged. Holds reference to the barlist to be merged into.""" def __init__(self, name, merge_into): ScoreSection.__init__(self, name) self.merge_barlist = merge_into class LyricsSection(ScoreSection): """ Holds the lyrics information. Will eventually be merged to the corresponding note in the section set by the voice id. """ def __init__(self, name, voice_id): ScoreSection.__init__(self, name) self.voice_id = voice_id class ScorePart(ScoreSection): """ object to keep track of part """ def __init__(self, staves=0, part_id=None, to_part=None, name=''): ScoreSection.__init__(self, name) self.part_id = part_id self.to_part = to_part self.abbr = '' self.midi = '' self.staves = staves def __repr__(self): return '<{0} {1} {2}>'.format( self.__class__.__name__, self.name, self.part_id) def set_first_bar(self, divisions): initime = [4, 4] iniclef = ('G', 2, 0) def check_time(bar): for obj in bar.obj_list: if isinstance(obj, BarAttr): if obj.time: return True if isinstance(obj, BarMus): return False def check_clef(bar): for obj in bar.obj_list: if isinstance(obj, BarAttr): if obj.clef or obj.multiclef: return True if isinstance(obj, BarMus): return False if not check_time(self.barlist[0]): try: self.barlist[0].obj_list[0].set_time(initime, False) except AttributeError: print("Warning can't set initial time sign!") if not check_clef(self.barlist[0]): try: self.barlist[0].obj_list[0].set_clef(iniclef) except AttributeError: print("Warning can't set initial clef sign!") self.barlist[0].obj_list[0].divs = divisions if self.staves: self.barlist[0].obj_list[0].staves = self.staves def merge_part_to_part(self): """Merge the part with the one indicated.""" if self.to_part.barlist: self.to_part.merge_voice(self) else: self.to_part.barlist.extend(self.barlist) class Bar(): """ Representing the bar/measure. Contains also information about how complete it is.""" def __init__(self): self.obj_list = [] self.list_full = False def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.obj_list) def add(self, obj): self.obj_list.append(obj) def has_music(self): """ Check if bar contains music. """ for obj in self.obj_list: if isinstance(obj, BarMus): return True return False def create_backup(self): """ Calculate and create backup object.""" b = 0 s = 1 for obj in self.obj_list: if isinstance(obj, BarMus): if not obj.chord: b += obj.duration[0] s *= obj.duration[1] elif isinstance(obj, BarBackup): break self.add(BarBackup((b, s))) def is_skip(self, obj_list=None): """ Check if bar has nothing but skips. """ if not obj_list: obj_list = self.obj_list for obj in obj_list: if obj.has_attr(): return False if isinstance(obj, BarNote): return False elif isinstance(obj, BarRest): if not obj.skip: return False return True def inject_voice(self, new_voice): """ Adding new voice to bar. Omitting double or conflicting bar attributes. Omitting also bars with only skips.""" if new_voice.obj_list[0].has_attr(): if self.obj_list[0].has_attr(): self.obj_list[0].merge_attr(new_voice.obj_list[0]) else: self.obj_list.insert(0, new_voice.obj_list[0]) backup_list = new_voice.obj_list[1:] else: backup_list = new_voice.obj_list try: if self.obj_list[-1].barline and new_voice.obj_list[-1].barline: self.obj_list.pop() except AttributeError: pass if not self.is_skip(backup_list): self.create_backup() for bl in backup_list: self.add(bl) class BarMus(): """ Common class for notes and rests. """ def __init__(self, duration, voice=1): self.duration = duration self.type = None self.tuplet = [] self.dot = 0 self.voice = voice self.staff = 0 self.chord = False self.other_notation = None self.dynamic = [] self.oct_shift = None def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.duration) def set_tuplet(self, fraction, ttype, nr, acttype='', normtype=''): self.tuplet.append(Tuplet(fraction, ttype, nr, acttype, normtype)) def set_staff(self, staff): self.staff = staff def add_dot(self): self.dot += 1 def add_other_notation(self, other): self.other_notation = other def set_dynamics(self, mark=None, wedge=None, before=True): if mark: sign = mark is_mark = True if wedge: sign = wedge is_mark = False self.dynamic.append(Dynamics(sign, before, is_mark)) def set_oct_shift(self, plac, octdir, size): self.oct_shift = OctaveShift(plac, octdir, size) def has_attr(self): return False ## # Classes that are used by BarMus ## class OctaveShift(): """Class for octave shifts.""" def __init__(self, plac, octdir, size): self.plac = plac self.octdir = octdir self.size = size class Dynamics(): """Stores information about dynamics. """ def __init__(self, sign, before=True, is_mark=False, ): self.before = before self.is_mark = is_mark self.sign = sign class Tuplet(): """Stores information about tuplet.""" def __init__(self, fraction, ttype, nr, acttype, normtype): self.fraction = fraction self.ttype = ttype self.nr = nr self.acttype = acttype self.normtype = normtype class Slur(): """Stores information about slur.""" def __init__(self, nr, slurtype): self.nr = nr self.slurtype = slurtype ## # Subclasses of BarMus ## class BarNote(BarMus): """ object to keep track of note parameters """ def __init__(self, pitch_note, alter, accidental, duration, voice=1): BarMus.__init__(self, duration, voice) self.base_note = pitch_note.upper() self.alter = alter self.octave = None self.accidental_token = accidental self.tie = [] self.grace = (0, 0) self.gliss = None self.tremolo = ('', 0) self.skip = False self.slur = [] self.artic = [] self.ornament = None self.adv_ornament = None self.fingering = None self.lyric = None def set_duration(self, duration, durtype=''): self.duration = duration self.dot = 0 if durtype: self.type = durtype def set_durtype(self, durtype): self.type = durtype def set_octave(self, octave): self.octave = octave def set_tie(self, tie_type): self.tie.append(tie_type) def set_slur(self, nr, slur_type): self.slur.append(Slur(nr, slur_type)) def add_articulation(self, art_name): self.artic.append(art_name) def add_ornament(self, ornament): self.ornament = ornament def add_adv_ornament(self, ornament, end_type="start"): self.adv_ornament = (ornament, {"type": end_type}) def set_grace(self, slash): self.grace = (1, slash) def set_gliss(self, line, endtype = "start", nr=1): if not line: line = "solid" self.gliss = (line, endtype, nr) def set_tremolo(self, trem_type, duration=False): if duration: self.tremolo = (trem_type, dur2lines(duration)) else: self.tremolo = (trem_type, self.tremolo[1]) def add_fingering(self, finger_nr): self.fingering = finger_nr def add_lyric(self, lyric_list): if not self.lyric: self.lyric = [] self.lyric.append(lyric_list) def change_lyric_syll(self, index, syll): self.lyric[index][1] = syll def change_lyric_nr(self, index, nr): self.lyric[index][2] = nr class Unpitched(BarNote): """Object to keep track of unpitched notes.""" def __init__(self, duration, step=None, voice=1): BarNote.__init__(self, 'B', 0, "", duration, voice=1) self.octave = 4 if step: self.base_note = step.upper() class BarRest(BarMus): """ object to keep track of different rests and skips """ def __init__(self, duration, voice=1, show_type=True, skip=False, pos=0): BarMus.__init__(self, duration, voice) self.show_type = show_type self.type = None self.skip = skip self.pos = pos def set_duration(self, duration, durtype=''): self.duration = duration if durtype: if self.show_type: self.type = durtype else: self.type = None def set_durtype(self, durtype): if self.show_type: self.type = durtype class BarAttr(): """ object that keep track of bar attributes, e.g. time sign, clef, key etc """ def __init__(self): self.key = None self.time = 0 self.clef = 0 self.mode = '' self.divs = 0 self.barline = None self.repeat = None self.staves = 0 self.multiclef = [] self.tempo = None def __repr__(self): return '<{0}>'.format(self.__class__.__name__) def set_key(self, muskey, mode): self.key = muskey self.mode = mode def set_time(self, fractlist, numeric=True): self.time = fractlist if not numeric and (fractlist == [2, 2] or fractlist == [4, 4]): self.time.append('common') def set_clef(self, clef): self.clef = clef def set_barline(self, bl): self.barline = convert_barl(bl) def set_tempo(self, unit=0, unittype='', beats=0, dots=0, text=""): self.tempo = TempoDir(unit, unittype, beats, dots, text) def has_attr(self): check = False if self.key is not None: check = True elif self.time != 0: check = True elif self.clef != 0: check = True elif self.multiclef: check = True elif self.divs != 0: check = True return check def merge_attr(self, barattr): """Merge in attributes (from another bar).""" if self.key is None and barattr.key is not None: self.key = barattr.key self.mode = barattr.mode if self.time == 0 and barattr.time != 0: self.time = barattr.time if self.clef == 0 and barattr.clef != 0: self.clef = barattr.clef if barattr.multiclef: self.multiclef += barattr.multiclef if self.tempo is None and barattr.tempo is not None: self.tempo = barattr.tempo class BarBackup(): """ Object that stores duration for backup """ def __init__(self, duration): self.duration = duration class TempoDir(): """ Object that stores tempo direction information """ def __init__(self, unit, unittype, beats, dots, text): if unittype: self.metr = unittype, beats self.midi = self.set_midi_tempo(unit, beats, dots) else: self.metr = 0 self.midi = 0 self.dots = dots self.text = text def set_midi_tempo(self, unit, beats, dots): u = Fraction(1, int(unit)) if dots: import math den = int(math.pow(2, dots)) num = int(math.pow(2, dots+1)-1) u *= Fraction(num, den) mult = 4*u return float(Fraction(beats)*mult) ## # Translation functions ## def dur2lines(dur): if dur == 8: return 1 elif dur == 16: return 2 elif dur == 32: return 3 else: return 0 def convert_barl(bl): if bl == '|': return 'regular' elif bl == ':': return 'dotted' elif bl == 'dashed': return bl elif bl == '.': return 'heavy' elif bl == '||': return 'light-light' elif bl == '.|' or bl == 'forward': return 'heavy-light' elif bl == '.|.': return 'heavy-heavy' elif bl == '|.' or bl == 'backward': return 'light-heavy' elif bl == "'": return 'tick' python-ly-0.9.3/ly/cli/0000775000175000017500000000000012636745216016041 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/cli/main.py0000644000175000017500000002124212621562064017326 0ustar wilbertwilbert00000000000000# 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 entry point for the 'ly' command. """ from __future__ import unicode_literals import contextlib import copy import io import os import shutil import sys import ly.pkginfo from . import setvar def usage(): """Print usage info.""" from . import doc sys.stdout.write(doc.__doc__) def usage_short(): """Print short usage info.""" sys.stdout.write("""\ Usage: ly [options] commands file, ... A tool for manipulating LilyPond source files See ly -h for a full list of commands and options. """) def version(): """Print version info.""" sys.stdout.write("ly {0}\n".format(ly.pkginfo.version)) def die(message): """Exit with message to STDERR.""" sys.stderr.write("error: " + message + '\n') sys.stderr.write( "See ly -h for a full list of commands and options.\n") sys.exit(1) class Options(object): """Store all the startup options and their defaults.""" def __init__(self): self.mode = None self.in_place = False self.encoding = 'UTF-8' self.output_encoding = None self.output = None self.replace_pattern = True self.backup_suffix = '~' self.with_filename = None self.default_language = "nederlands" self.indent_width = 2 self.indent_tabs = False self.tab_width = 8 self.inline_style = False self.stylesheet = None self.number_lines = False def set_variable(self, name, value): name = name.replace('-', '_') try: func = getattr(setvar, name) except AttributeError: die("unknown variable: {name}".format(name=name)) try: value = func(value) except ValueError as e: die(format(e)) setattr(self, name, value) class Output(object): """Object living for a whole file/command operation, handling the output. When opening a file it has already opened earlier, the file is appended to (like awk). """ def __init__(self): self._seen_filenames = set() def get_filename(self, opts, filename): """Queries the output attribute from the Options and returns it. If replace_pattern is True (by default) and the attribute contains a '*', it is replaced with the full path of the specified filename, but without extension. It the attribute contains a '?', it is replaced with the filename without path and extension. If '-' is returned, it denotes standard output. """ if not opts.output: return '-' elif opts.replace_pattern: path, ext = os.path.splitext(filename) directory, name = os.path.split(path) return opts.output.replace('?', name).replace('*', path) else: return opts.output @contextlib.contextmanager def file(self, opts, filename, encoding): """Return a context manager for writing to. If you set encoding to "binary" or False, the file is opened in binary mode and you should encode the data you write yourself. """ if not filename or filename == '-': filename, mode = sys.stdout.fileno(), 'w' else: if filename not in self._seen_filenames: self._seen_filenames.add(filename) if opts.backup_suffix and os.path.exists(filename): shutil.copy(filename, filename + opts.backup_suffix) mode = 'w' else: mode = 'a' if encoding in (False, "binary"): f = io.open(filename, mode + 'b') else: f = io.open(filename, mode, encoding=encoding) try: yield f finally: f.close() def parse_command_line(): """Return a three-tuple(options, commands, files). options is an Options instance with all the command-line options commands is a list of command.command instances files is the list of filename arguments Also performs error handling and may exit on certain circumstances. """ if len(sys.argv) < 2: usage_short() sys.exit(2) if isinstance(sys.argv[0], type('')): # python 3 - arguments are unicode strings args = iter(sys.argv[1:]) else: # python 2 - arguments are bytes, decode them fsenc = sys.getfilesystemencoding() or 'latin1' args = (a.decode(fsenc) for a in sys.argv[1:]) opts = Options() commands = [] files = [] def next_arg(message): """Get the next argument, if missing, die with message.""" try: return next(args) except StopIteration: die(message) for arg in args: if arg in ('-h', '--help'): usage() sys.exit(0) elif arg in ('-v', '--version'): version() sys.exit(0) elif arg in ('-i', '--in-place'): opts.in_place = True elif arg in ('-o', '--output'): opts.output = next_arg("missing output filename") elif arg == '-d': s = next_arg("missing variable=value") try: name, value = s.split('=', 1) except ValueError: die("missing '=' in variable set") opts.set_variable(name, value) elif arg in ('-e', '--encoding'): opts.encoding = next_arg("missing encoding name") elif arg == '--output-encoding': opts.output_encoding = next_arg("missing output encoding name") elif arg in ('-l', '--language'): s = next_arg("missing language name") opts.set_variable("default-language", s) elif arg == '--': files.extend(args) elif arg.startswith('-'): die('unknown option: ' + arg) elif not commands: commands = parse_command(arg) else: files.append(arg) from . import command if not commands or isinstance(commands[-1], command._edit_command): commands.append(command.write()) if not files: files.append('-') if opts.with_filename is None: opts.with_filename = len(files) > 1 return opts, commands, files def parse_command(arg): """Parse the command string, returning a list of command.command instances. Exits when a command is invalid. """ from . import command result = [] for c in arg.split(';'): args = c.split(None, 1) if args: if '=' in args[0]: args = ['set_variable', c] cmd = args.pop(0) try: result.append(getattr(command, cmd.replace('-', '_'))(*args)) except AttributeError: die("unknown command: " + cmd) except (TypeError, ValueError): die("invalid arguments: " + c) return result def load(filename, encoding, mode): """Load a file, returning a ly.document.Document""" import ly.document if filename == '-': doc = ly.document.Document.load(sys.stdin.fileno(), encoding, mode) doc.filename = '-' else: doc = ly.document.Document.load(filename, encoding, mode) return doc def main(): opts, commands, files = parse_command_line() import ly.document output = Output() exit_code = 0 for filename in files: options = copy.deepcopy(opts) try: doc = load(filename, options.encoding, options.mode) except IOError as err: sys.stderr.write('warning: skipping file "{0}":\n {1}\n'.format(filename, err)) exit_code = 1 continue cursor = ly.document.Cursor(doc) for c in commands: c.run(options, cursor, output) return exit_code python-ly-0.9.3/ly/cli/command.py0000644000175000017500000001707412525203625020026 0ustar wilbertwilbert00000000000000# 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 commands that are available to the command line. """ from __future__ import unicode_literals import re import sys import ly.docinfo import ly.indent import ly.pitch import ly.reformat class _command(object): """Base class for commands. If the __init__() fails with TypeError or ValueError, the command is considered invalid and an error message will be written to the console in the parse_command() function in main.py. By default, __init__() expects no arguments. If your command does accept arguments, they are provided in a single argument that you should parse yourself. """ def __init__(self): pass def run(self, opts, cursor, output): pass class set_variable(_command): """set a variable to a value""" def __init__(self, arg): self.name, self.value = arg.split('=', 1) def run(self, opts, cursor, output): opts.set_variable(self.name, self.value) class _info_command(_command): """base class for commands that print some output to stdout.""" def run(self, opts, cursor, output): info = ly.docinfo.DocInfo(cursor.document) text = self.get_info(info) if text: if opts.with_filename: text = cursor.document.filename + ":" + text sys.stdout.write(text + '\n') def get_info(self, info): """Should return the desired information from the docinfo object. If it returns None or an empty string, nothing is printed. """ raise NotImplementedError() class mode(_info_command): """print mode to stdout""" def get_info(self, info): return info.mode() class version(_info_command): """print version to stdout""" def get_info(self, info): return info.version_string() class language(_info_command): """print language to stdout""" def get_info(self, info): return info.language() class _edit_command(_command): """a command that edits the source file""" pass class indent(_edit_command): """run the indenter""" def indenter(self, opts): """Get a ly.indent.Indenter initialized with our options.""" i = ly.indent.Indenter() i.indent_tabs = opts.indent_tabs i.indent_width = opts.indent_width return i def run(self, opts, cursor, output): self.indenter(opts).indent(cursor) class reformat(indent): """reformat the document""" def run(self, opts, cursor, output): ly.reformat.reformat(cursor, self.indenter(opts)) class translate(_edit_command): """translate pitch names""" def __init__(self, language): if language not in ly.pitch.pitchInfo: raise ValueError() self.language = language def run(self, opts, cursor, output): import ly.pitch.translate try: changed = ly.pitch.translate.translate(cursor, self.language, opts.default_language) except ly.pitch.PitchNameNotAvailable: sys.stderr.write( "warning: transate: pitch names not available in \"{0}\"\n" " skipping file: {1}\n".format(self.language, cursor.document.filename)) return if not changed: version = ly.docinfo.DocInfo(cursor.document).version() ly.pitch.translate.insert_language(cursor.document, self.language, version) class transpose(_edit_command): """transpose music""" def __init__(self, arg): result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", arg): r = ly.pitch.pitchReader("nederlands")(pitch) if r: result.append(ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) self.from_pitch, self.to_pitch = result def run(self, opts, cursor, output): import ly.pitch.transpose transposer = ly.pitch.transpose.Transposer(self.from_pitch, self.to_pitch) try: ly.pitch.transpose.transpose(cursor, transposer, opts.default_language) except ly.pitch.PitchNameNotAvailable: language = ly.docinfo.DocInfo(cursor.document).language() or opts.default_language sys.stderr.write( "warning: transpose: pitch names not available in \"{0}\"\n" " skipping file: {1}\n".format(language, cursor.document.filename)) class rel2abs(_edit_command): """convert relative music to absolute""" def run(self, opts, cursor, output): import ly.pitch.rel2abs ly.pitch.rel2abs.rel2abs(cursor, opts.default_language) class abs2rel(_edit_command): """convert absolute music to relative""" def run(self, opts, cursor, output): import ly.pitch.abs2rel ly.pitch.abs2rel.abs2rel(cursor, opts.default_language) class _export_command(_command): """Command that exports to a file.""" def __init__(self, output=None): self.output = output class musicxml(_export_command): def run(self, opts, cursor, output): import ly.musicxml writer = ly.musicxml.writer() writer.parse_text(cursor.document.plaintext()) xml = writer.musicxml() if self.output: filename = self.output else: filename = output.get_filename(opts, cursor.document.filename) encoding = opts.output_encoding or "utf-8" with output.file(opts, filename, "binary") as f: xml.write(f, encoding) class write(_command): """write the source file.""" def __init__(self, output=None): self.output = output def run(self, opts, cursor, output): # determine the real output filename to use encoding = opts.output_encoding or opts.encoding if self.output: filename = self.output elif opts.in_place: if not cursor.document.modified and encoding == opts.encoding: return filename = cursor.document.filename else: filename = output.get_filename(opts, cursor.document.filename) with output.file(opts, filename, encoding) as f: f.write(cursor.document.plaintext()) class highlight(_export_command): """write syntax colored HTML.""" def run(self, opts, cursor, output): import ly.colorize w = ly.colorize.HtmlWriter() w.inline_style = opts.inline_style w.stylesheet_ref = opts.stylesheet w.number_lines = opts.number_lines w.title = cursor.document.filename w.encoding = opts.output_encoding or "utf-8" doc = w.html(cursor) if self.output: filename = self.output else: filename = output.get_filename(opts, cursor.document.filename) with output.file(opts, filename, w.encoding) as f: f.write(doc) hl = highlight python-ly-0.9.3/ly/cli/__init__.py0000644000175000017500000000165512461001227020136 0ustar wilbertwilbert00000000000000# 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 commandline interface of the 'ly' command. """ python-ly-0.9.3/ly/cli/doc.py0000644000175000017500000001377212504111516017150 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2015 - 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. # this module only contains a doc string, which is printed when using # ly -h . """ Usage:: ly [options] commands file, ... A tool for manipulating LilyPond source files Options ------- -v, --version show version number and exit -h, --help show this help text and exit -i, --in-place overwrite input files -o, --output NAME output file name -e, --encoding ENC (input) encoding (default UTF-8) --output-encoding ENC output encoding (default to input encoding) -l, --language NAME default pitch name language (default to "nederlands") -d set a variable The special option ``--`` considers the remaining arguments to be file names. Arguments --------- The command is one argument with semicolon-separated commands. In most cases you'll quote the command so that it is seen as one argument. You can specify more than one LilyPond file. If you want to process many files and write the results of the operations on each file to a separate output file, you can use two special characters in the output filename: a '*' will be replaced with the full path name of the current input file (without extension), and a '?' will be replaced with the input filename (without path and extension). If you don't want to have '*' or '?' replaced in the output filename, you can set ``-d replace-pattern=false``. If you don't specify input or output filenames, standard input is read and standard output is written to. Commands -------- Informative commands that write information to standard output and do not change the file: ``mode`` print the mode (guessing if not given) of the document ``version`` print the LilyPond version, if set in the document ``language`` print the pitch name language, if set in the document Commands that change the file: ``indent`` re-indent the file ``reformat`` reformat the file ``translate `` translate the pitch names to the language ``transpose `` transpose the file like LilyPond would do, pitches are given in the 'nederlands' language ``abs2rel`` convert absolute music to relative ``rel2abs`` convert relative music to absolute ``write [filename]`` write the file to the given filename or the output variable. If the last command was an editing command, write is automatically called. Commands that export the file to another format: ``musicxml [filename]`` export to MusicXML (in development, far from complete) ``highlight [filename]`` export the document as syntax colored HTML ``hl [filename]`` alias for highlight Between commands, you can set or unset a variable using: ``variable=value`` set a variable to value. Special values are true, false, which are interpreted as boolean values, or digits, which will be interpreted as integer values. ``variable=`` unset a variable Variables --------- The following variables can be set to influence the behaviour of commands. If there is a default value, it is written between brackets: ``mode`` mode of the file to read (default automatic) can be one of: lilypond, scheme, latex, html, docbook, texinfo. ``output`` [-] the output filename (also set by -o argument) ``encoding`` [UTF-8] encoding to read (also set by -e argument) ``default-language`` [nederlands] the pitch names language to use by default, when not specified otherwise in the document ``output-encoding`` encoding to write (defaults to ``encoding``, also set by the ``--output-encoding`` argument) ``in-place`` [``false``] whether to overwrite input files (same as ``-i``) ``backup-suffix`` [~] suffix to use when editing files in-place, if set, backs up the original file before overwriting it ``replace-pattern`` [``true``] whether to replace '*' and '?' in the output filename. ``indent-tabs`` [``false``] whether to use tabs for indent ``indent-width`` [2] how many spaces for each indent level (if not using tabs) ``stylesheet`` filename to reference as an external stylesheet for syntax-highlighted HTML. This filename is literally used in the ```` tag. ``inline-style`` [``false``] whether to use inline style attributes for syntax-highlighted HTML. By default a css stylesheet is embedded. ``number-lines`` [``false``] whether to add line numbers when creating syntax-highlighted HTML. These variables influence the output of information commands: ``with-filename`` prints the filename next to information like version, etc. This is ``true`` by default if there is more than one file specified. Examples -------- Here is an example to re-indent and transpose a LilyPond file:: ly "indent; transpose c d" -o output.ly file.ly Examples using the '*' in the output file name:: ly "transpose c d" *.ly -o '*-transposed.ly' ly highlight *.ly -o 'html/?.html' """ python-ly-0.9.3/ly/cli/setvar.py0000644000175000017500000000630512503725630017710 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2015 - 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. """ Variables, checking and defaults. """ from __future__ import unicode_literals def _check_bool(name, value): """Check for boolean value.""" if value.lower() in ('yes', 'on', 'true'): return True elif value.lower() in ('no', 'off', 'false'): return False elif value.isdigit(): return bool(int(value)) raise ValueError( "{name}: ambiguous boolean value: {value}".format( name=name, value=value)) def _check_int(name, value): """Check for integer value.""" if value.isdigit(): return int(value) raise ValueError("{name}: not an integer value: {value}".format( name=name, value=value)) def mode(arg): import ly.lex if arg is not None and arg not in ly.lex.modes: raise ValueError("unknown mode: {mode}".format(mode=arg)) return mode def in_place(arg): return _check_bool("in-place", arg) def encoding(arg): import codecs try: codecs.lookup(arg) except LookupError: raise ValueError("encoding: unknown encoding: {encoding}".format( encoding=encoding)) return arg def output_encoding(arg): if arg: import codecs try: codecs.lookup(arg) except LookupError: raise ValueError("output-encoding: unknown encoding: {encoding}".format( encoding=encoding)) return arg return None def output(arg): return arg or None def replace_pattern(arg): return _check_bool("replace-pattern", arg) def backup_suffix(arg): if "/" in arg: raise ValueError("/ not allowed in backup-suffix") def with_filename(arg): if arg: return _check_bool("with-filename", arg) return None def default_language(arg): import ly.pitch if arg: if arg not in ly.pitch.pitchInfo: raise ValueError("unknown pitch language: {language}".format( language=arg)) return arg return "nederlands" def indent_width(arg): return _check_int("indent-width", arg) def indent_tabs(arg): return _check_bool("indent-tabs", arg) def tab_width(arg): return _check_int("tab-width", arg) def inline_style(arg): return _check_bool("inline-style", arg) def stylesheet(arg): return arg or None def number_lines(arg): return _check_bool("number-lines", arg) python-ly-0.9.3/ly/cursortools.py0000644000175000017500000000547712461001227020234 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2013 - 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. """ Routines manipulating ly.document.Cursor instances. """ from __future__ import unicode_literals import ly.lex import ly.document def find_indent(iterable): """Yield (token, is_indent, nest) for every occurring indent/dedent token. The tokens are yielded from the specified iterable. """ nest = 0 for token in iterable: if isinstance(token, ly.lex.Indent): nest += 1 yield token, True, nest elif isinstance(token, ly.lex.Dedent): nest -= 1 yield token, False, nest def select_block(cursor): """Try to select a meaningful block. Searches backwards for an indenting token, then selects up to the corresponding dedenting token. If needed searches an extra level back to always extend the selection. Returns True if the cursor's selection has changed. """ end = cursor.end if cursor.end is not None else cursor.document.size() tokens = ly.document.Runner.at(cursor, after_token=True) # search backwards to the first indenting token for token, isindent, nest in find_indent(tokens.backward()): if isindent and nest == 1: pos1 = tokens.position() startpoint = tokens.copy() # found, now look forward for token, isindent, nest in find_indent(tokens.forward()): if not isindent and nest < 0 and tokens.position() + len(token) >= end: # we found the endpoint pos2 = tokens.position() + len(token) if nest < -1: threshold = 1 - nest for token, isindent, nest in find_indent(startpoint.backward()): if isindent and nest == threshold: pos1 = tokens.position() break cursor.start, cursor.end = pos1, pos2 return True return python-ly-0.9.3/ly/lex/0000775000175000017500000000000012636745216016062 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/lex/__init__.py0000644000175000017500000001131112461001227020145 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. r""" This module is built on top of slexer and can parse LilyPond input and other formats. The base functionality is delegated to modules with an underscore in this package. The modules describing parsing modes (filetypes) are the files without underscore. Currently available are modes for lilypond, latex, html, texinfo, scheme, and docbook. The 'underscored' modules should not be imported in application code. What is needed from them is available here, in the ly.lex namespace. If you add new files for parsing other file types, you should add them in _mode.py. The _token.py module contains base Token types and Token mixin classes. The State, Parser, FallthroughParser and Fridge classes from slexer are all slightly extended here, Usage:: >>> import ly.lex >>> txt = r"\relative c' { c d e f-^ g }" >>> s = ly.lex.state("lilypond") >>> for t in s.tokens(txt): ... print(t, t.__class__.__name__) \relative Command Space c Name ' Unparsed Space { SequentialStart Space c Note Space d Note Space e Note Space f Note - Direction ^ ScriptAbbreviation Space g Note Space } SequentialEnd A State() is used to parse text. The text is given to the tokens() method, that returns an iterator iterating over Token instances as they are found. Each token has a 'pos' and an 'end' attribute describing its position in the original string. While iterating over the tokens(), the State maintains information about what kind of text is parsed. (So don't iterate over more than one call to tokens() of the same State object at the same time.) Use ly.lex.state("name") to get a state for a specific mode to start parsing with. If you don't know the type of text, you can use ly.lex.guessState(text), where text is the text you want to parse. A quick heuristic is then used to determine the type of the text. See for more information the documentation of the slexer module. """ from __future__ import unicode_literals import re from .. import slexer from ._token import * from ._mode import extensions, modes, guessMode __all__ = [ 'State', 'Parser', 'FallthroughParser', 'Fridge', 'extensions', 'modes', 'guessMode', 'state', 'guessState', 'Token', 'Unparsed', 'Space', 'Newline', 'Comment', 'LineComment', 'BlockComment', 'BlockCommentStart', 'BlockCommentEnd', 'String', 'StringStart', 'StringEnd', 'Character', 'Numeric', 'Error', 'MatchStart', 'MatchEnd', 'Indent', 'Dedent', ] class Parser(slexer.Parser): re_flags = re.MULTILINE | re.UNICODE argcount = 0 default = Unparsed mode = None def __init__(self, argcount = None): if argcount is not None: self.argcount = argcount def freeze(self): return (self.argcount,) class FallthroughParser(Parser, slexer.FallthroughParser): pass class State(slexer.State): def endArgument(self): """Decrease argcount and leave the parser if it would reach 0.""" while self.depth() > 1: p = self.parser() if p.argcount == 1: self.leave() else: if p.argcount > 0: p.argcount -= 1 return def mode(self): """Returns the mode attribute of the first parser (from current parser) that has it.""" for parser in self.state[::-1]: if parser.mode: return parser.mode class Fridge(slexer.Fridge): def __init__(self, stateClass = State): super(Fridge, self).__init__(stateClass) def state(mode): """Returns a State instance for the given mode.""" return State(modes[mode]()) def guessState(text): """Returns a State instance, guessing the type of text.""" return State(modes[guessMode(text)]()) python-ly-0.9.3/ly/lex/lilypond.py0000644000175000017500000010642412500145341020252 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Parses and tokenizes LilyPond input. """ from __future__ import unicode_literals import itertools from . import _token from . import Parser, FallthroughParser re_articulation = r"[-_^][_.>|!+^-]" re_dynamic = ( r"\\[]|" r"\\(f{1,5}|p{1,5}" r"|mf|mp|fp|spp?|sff?|sfz|rfz" r"|cresc|decresc|dim|cr|decr" r")(?![A-Za-z])") re_duration = r"(\\(maxima|longa|breve)\b|(1|2|4|8|16|32|64|128|256|512|1024|2048)(?!\d))" re_dot = r"\." re_scaling = r"\*[\t ]*\d+(/\d+)?" # an identifier allowing letters and single hyphens in between re_identifier = r"[^\W\d_]+([_-][^\W\d_]+)*" # the lookahead pattern for the end of an identifier (ref) re_identifier_end = r"(?![_-]?[^\W\d])" class Identifier(_token.Token): """A variable name, like ``some-variable``.""" rx = r"(?>" matchname = "simultaneous" def update_state(self, state): state.leave() state.endArgument() class SequentialStart(OpenBracket): def update_state(self, state): state.enter(ParseMusic()) class SequentialEnd(CloseBracket): pass class SimultaneousStart(OpenSimultaneous): def update_state(self, state): state.enter(ParseMusic()) class SimultaneousEnd(CloseSimultaneous): pass class PipeSymbol(Delimiter): rx = r"\|" class Articulation(_token.Token): """Base class for articulation things.""" class ArticulationCommand(Articulation, IdentifierRef): @classmethod def test_match(cls, match): s = match.group()[1:] if '-' not in s: from .. import words for l in ( words.articulations, words.ornaments, words.fermatas, words.instrument_scripts, words.repeat_scripts, words.ancient_scripts, ): if s in l: return True return False class Direction(_token.Token): rx = r"[-_^]" def update_state(self, state): state.enter(ParseScriptAbbreviationOrFingering()) class ScriptAbbreviation(Articulation, _token.Leaver): rx = r"[+|!>._^-]" class Fingering(Articulation, _token.Leaver): rx = r"\d" class StringNumber(Articulation): rx = r"\\\d+" class Slur(_token.Token): pass class SlurStart(Slur, _token.MatchStart): rx = r"\(" matchname = "slur" class SlurEnd(Slur, _token.MatchEnd): rx = r"\)" matchname = "slur" class PhrasingSlurStart(SlurStart): rx = r"\\\(" matchname = "phrasingslur" class PhrasingSlurEnd(SlurEnd): rx = r"\\\)" matchname = "phrasingslur" class Tie(Slur): rx = r"~" class Beam(_token.Token): pass class BeamStart(Beam, _token.MatchStart): rx = r"\[" matchname = "beam" class BeamEnd(Beam, _token.MatchEnd): rx = r"\]" matchname = "beam" class Ligature(_token.Token): pass class LigatureStart(Ligature, _token.MatchStart): rx = r"\\\[" matchname = "ligature" class LigatureEnd(Ligature, _token.MatchEnd): rx = r"\\\]" matchname = "ligature" class Tremolo(_token.Token): pass class TremoloColon(Tremolo): rx = r":" def update_state(self, state): state.enter(ParseTremolo()) class TremoloDuration(Tremolo, _token.Leaver): rx = r"\b(8|16|32|64|128|256|512|1024|2048)(?!\d)" class ChordItem(_token.Token): """Base class for chordmode items.""" class ChordModifier(ChordItem): rx = r"((? 0: state.leave() state.leave() state.endArgument() class Repeat(Command): rx = r"\\repeat(?![A-Za-z])" def update_state(self, state): state.enter(ParseRepeat()) class RepeatSpecifier(Specifier): @_token.patternproperty def rx(): from .. import words return r"\b({0})(?![A-Za-z])".format("|".join(words.repeat_types)) class RepeatCount(IntegerValue, _token.Leaver): pass class Tempo(Command): rx = r"\\tempo\b" def update_state(self, state): state.enter(ParseTempo()) class TempoSeparator(Delimiter): rx = r"[-~](?=\s*\d)" class Override(Keyword): rx = r"\\override\b" def update_state(self, state): state.enter(ParseOverride()) class Set(Override): rx = r"\\set\b" def update_state(self, state): state.enter(ParseSet()) class Revert(Override): rx = r"\\revert\b" def update_state(self, state): state.enter(ParseRevert()) class Unset(Keyword): rx = r"\\unset\b" def update_state(self, state): state.enter(ParseUnset()) class Tweak(Keyword): rx = r"\\tweak\b" def update_state(self, state): state.enter(ParseTweak()) class Translator(Command): def update_state(self, state): state.enter(ParseTranslator()) class New(Translator): rx = r"\\new\b" class Context(Translator): rx = r"\\context\b" class Change(Translator): rx = r"\\change\b" class AccidentalStyle(Command): rx = r"\\accidentalStyle\b" def update_state(self, state): state.enter(ParseAccidentalStyle()) class AccidentalStyleSpecifier(Specifier): @_token.patternproperty def rx(): from .. import words return r"\b({0})(?!-?\w)".format("|".join(words.accidentalstyles)) class AlterBroken(Command): rx = r"\\alterBroken\b" def update_state(self, state): state.enter(ParseAlterBroken()) class Clef(Command): rx = r"\\clef\b" def update_state(self, state): state.enter(ParseClef()) class ClefSpecifier(Specifier): @_token.patternproperty def rx(): from .. import words return r"\b({0})\b".format("|".join(words.clefs_plain)) def update_state(self, state): state.leave() class PitchCommand(Command): rx = r"\\(relative|transpose|transposition|key|octaveCheck)\b" def update_state(self, state): argcount = 2 if self == '\\transpose' else 1 state.enter(ParsePitchCommand(argcount)) class KeySignatureMode(Command): @_token.patternproperty def rx(): from .. import words return r"\\({0})(?![A-Za-z])".format("|".join(words.modes)) class Hide(Keyword): rx = r"\\hide\b" def update_state(self, state): state.enter(ParseHideOmit()) class Omit(Keyword): rx = r"\\omit\b" def update_state(self, state): state.enter(ParseHideOmit()) class Unit(Command): rx = r"\\(mm|cm|in|pt)\b" class InputMode(Command): pass class LyricMode(InputMode): rx = r"\\(lyricmode|((old)?add)?lyrics|lyricsto)\b" def update_state(self, state): state.enter(ExpectLyricMode()) class Lyric(_token.Item): """Base class for Lyric items.""" class LyricText(Lyric): rx = r"[^\\\s\d\"]+" class LyricHyphen(Lyric): rx = r"--(?=($|[\s\\]))" class LyricExtender(Lyric): rx = r"__(?=($|[\s\\]))" class LyricSkip(Lyric): rx = r"_(?=($|[\s\\]))" class Figure(_token.Token): """Base class for Figure items.""" class FigureStart(Figure): rx = r"<" def update_state(self, state): state.enter(ParseFigure()) class FigureEnd(Figure, _token.Leaver): rx = r">" class FigureBracket(Figure): rx = r"[][]" class FigureStep(Figure): """A step figure number or the underscore.""" rx = r"_|\d+" class FigureAccidental(Figure): """A figure accidental.""" rx = r"[-+!]+" class FigureModifier(Figure): """A figure modifier.""" rx = r"\\[\\!+]|/" class NoteMode(InputMode): rx = r"\\(notes|notemode)\b" def update_state(self, state): state.enter(ExpectNoteMode()) class ChordMode(InputMode): rx = r"\\(chords|chordmode)\b" def update_state(self, state): state.enter(ExpectChordMode()) class DrumMode(InputMode): rx = r"\\(drums|drummode)\b" def update_state(self, state): state.enter(ExpectDrumMode()) class FigureMode(InputMode): rx = r"\\(figures|figuremode)\b" def update_state(self, state): state.enter(ExpectFigureMode()) class UserCommand(IdentifierRef): pass class SimultaneousOrSequentialCommand(Keyword): rx = r"\\(simultaneous|sequential)\b" class SchemeStart(_token.Item): rx = "[#$](?![{}])" def update_state(self, state): from . import scheme state.enter(scheme.ParseScheme(1)) class ContextName(_token.Token): @_token.patternproperty def rx(): from .. import words return r"\b({0})\b".format("|".join(words.contexts)) class BackSlashedContextName(ContextName): @_token.patternproperty def rx(): from .. import words return r"\\({0})\b".format("|".join(words.contexts)) class GrobName(_token.Token): @_token.patternproperty def rx(): from .. import data return r"\b({0})\b".format("|".join(data.grobs())) class GrobProperty(Variable): rx = r"\b([a-z]+|[XY])(-([a-z]+|[XY]))*(?![\w])" class ContextProperty(Variable): @_token.patternproperty def rx(): from .. import data return r"\b({0})\b".format("|".join(data.context_properties())) class PaperVariable(Variable): """A variable inside Paper. Always follow this one by UserVariable.""" @classmethod def test_match(cls, match): from .. import words return match.group() in words.papervariables class HeaderVariable(Variable): """A variable inside Header. Always follow this one by UserVariable.""" @classmethod def test_match(cls, match): from .. import words return match.group() in words.headervariables class LayoutVariable(Variable): """A variable inside Header. Always follow this one by UserVariable.""" @classmethod def test_match(cls, match): from .. import words return match.group() in words.layoutvariables class Chord(_token.Token): """Base class for Chord delimiters.""" pass class ChordStart(Chord): rx = r"<" def update_state(self, state): state.enter(ParseChord()) class ChordEnd(Chord, _token.Leaver): rx = r">" class DrumChordStart(ChordStart): def update_state(self, state): state.enter(ParseDrumChord()) class DrumChordEnd(ChordEnd): pass class ErrorInChord(Error): rx = "|".join(( re_articulation, # articulation r"<<|>>", # double french quotes r"\\[\\\]\[\(\)()]", # slurs beams re_duration, # duration re_scaling, # scaling )) class Name(UserVariable): r"""A variable name without \ prefix.""" class EqualSign(_token.Token): rx = r"=" # Parsers class ParseLilyPond(Parser): mode = 'lilypond' # basic stuff that can appear everywhere space_items = ( _token.Space, BlockCommentStart, LineComment, ) base_items = space_items + ( SchemeStart, StringQuotedStart, ) # items that represent commands in both toplevel and music mode command_items = ( Repeat, PitchCommand, Override, Revert, Set, Unset, Hide, Omit, Tweak, New, Context, Change, With, Clef, Tempo, KeySignatureMode, AccidentalStyle, AlterBroken, SimultaneousOrSequentialCommand, ChordMode, DrumMode, FigureMode, LyricMode, NoteMode, MarkupStart, MarkupLines, MarkupList, ArticulationCommand, Keyword, Command, SimultaneousOrSequentialCommand, UserCommand, ) # items that occur in toplevel, book, bookpart or score # no Leave-tokens! toplevel_base_items = base_items + ( SequentialStart, SimultaneousStart, ) + command_items # items that occur in music expressions music_items = base_items + ( Dynamic, Skip, Spacer, Q, Rest, Note, Fraction, Length, Octave, OctaveCheck, AccidentalCautionary, AccidentalReminder, PipeSymbol, VoiceSeparator, SequentialStart, SequentialEnd, SimultaneousStart, SimultaneousEnd, ChordStart, ContextName, GrobName, SlurStart, SlurEnd, PhrasingSlurStart, PhrasingSlurEnd, Tie, BeamStart, BeamEnd, LigatureStart, LigatureEnd, Direction, StringNumber, IntegerValue, ) + command_items # items that occur inside chords music_chord_items = ( ErrorInChord, ChordEnd, ) + music_items class ParseGlobal(ParseLilyPond): """Parses LilyPond from the toplevel of a file.""" items = ( Book, BookPart, Score, MarkupStart, MarkupLines, MarkupList, Paper, Header, Layout, ) + toplevel_base_items + ( Name, DotPath, EqualSign, Fraction, DecimalValue, ) def update_state(self, state, token): if isinstance(token, EqualSign): state.enter(ParseGlobalAssignment()) class ParseGlobalAssignment(FallthroughParser, ParseLilyPond): items = space_items + ( Skip, Spacer, Q, Rest, Note, Length, Fraction, DecimalValue, Direction, StringNumber, Dynamic, ) class ExpectOpenBracket(FallthroughParser, ParseLilyPond): """Waits for an OpenBracket and then replaces the parser with the class set in the replace attribute. Subclass this to set the destination for the OpenBracket. """ default = Error items = space_items + ( OpenBracket, ) def update_state(self, state, token): if isinstance(token, OpenBracket): state.replace(self.replace()) class ExpectMusicList(FallthroughParser, ParseLilyPond): """Waits for an OpenBracket or << and then replaces the parser with the class set in the replace attribute. Subclass this to set the destination for the OpenBracket. """ items = space_items + ( OpenBracket, OpenSimultaneous, SimultaneousOrSequentialCommand, ) def update_state(self, state, token): if isinstance(token, (OpenBracket, OpenSimultaneous)): state.replace(self.replace()) class ParseScore(ParseLilyPond): r"""Parses the expression after ``\score {``, leaving at ``}`` """ items = ( CloseBracket, Header, Layout, Midi, With, ) + toplevel_base_items class ExpectScore(ExpectOpenBracket): replace = ParseScore class ParseBook(ParseLilyPond): r"""Parses the expression after ``\book {``, leaving at ``}`` """ items = ( CloseBracket, MarkupStart, MarkupLines, MarkupList, BookPart, Score, Paper, Header, Layout, ) + toplevel_base_items class ExpectBook(ExpectOpenBracket): replace = ParseBook class ParseBookPart(ParseLilyPond): r"""Parses the expression after ``\bookpart {``, leaving at ``}`` """ items = ( CloseBracket, MarkupStart, MarkupLines, MarkupList, Score, Paper, Header, Layout, ) + toplevel_base_items class ExpectBookPart(ExpectOpenBracket): replace = ParseBookPart class ParsePaper(ParseLilyPond): r"""Parses the expression after ``\paper {``, leaving at ``}`` """ items = base_items + ( CloseBracket, MarkupStart, MarkupLines, MarkupList, PaperVariable, UserVariable, EqualSign, DotPath, DecimalValue, Unit, ) class ExpectPaper(ExpectOpenBracket): replace = ParsePaper class ParseHeader(ParseLilyPond): r"""Parses the expression after ``\header {``, leaving at ``}`` """ items = ( CloseBracket, MarkupStart, MarkupLines, MarkupList, HeaderVariable, UserVariable, EqualSign, DotPath, ) + toplevel_base_items class ExpectHeader(ExpectOpenBracket): replace = ParseHeader class ParseLayout(ParseLilyPond): r"""Parses the expression after ``\layout {``, leaving at ``}`` """ items = base_items + ( CloseBracket, LayoutContext, LayoutVariable, UserVariable, EqualSign, DotPath, DecimalValue, Unit, ContextName, GrobName, ) + command_items class ExpectLayout(ExpectOpenBracket): replace = ParseLayout class ParseMidi(ParseLilyPond): r"""Parses the expression after ``\midi {``, leaving at ``}`` """ items = base_items + ( CloseBracket, LayoutContext, LayoutVariable, UserVariable, EqualSign, DotPath, DecimalValue, Unit, ContextName, GrobName, ) + command_items class ExpectMidi(ExpectOpenBracket): replace = ParseMidi class ParseWith(ParseLilyPond): r"""Parses the expression after ``\with {``, leaving at ``}`` """ items = ( CloseBracket, ContextName, GrobName, ContextProperty, EqualSign, DotPath, ) + toplevel_base_items class ExpectWith(ExpectOpenBracket): replace = ParseWith class ParseContext(ParseLilyPond): r"""Parses the expression after (``\layout {``) ``\context {``, leaving at ``}`` """ items = ( CloseBracket, BackSlashedContextName, ContextProperty, EqualSign, DotPath, ) + toplevel_base_items class ExpectContext(ExpectOpenBracket): replace = ParseContext class ParseMusic(ParseLilyPond): """Parses LilyPond music expressions.""" items = music_items + ( TremoloColon, ) class ParseChord(ParseMusic): """LilyPond inside chords ``< >``""" items = music_chord_items class ParseString(Parser): default = String items = ( StringQuotedEnd, StringQuoteEscape, ) class ParseBlockComment(Parser): default = BlockComment items = ( BlockCommentEnd, ) class ParseMarkup(Parser): items = ( MarkupScore, MarkupCommand, MarkupUserCommand, OpenBracketMarkup, CloseBracketMarkup, MarkupWord, ) + base_items class ParseRepeat(FallthroughParser): items = space_items + ( RepeatSpecifier, StringQuotedStart, RepeatCount, ) class ParseTempo(FallthroughParser): items = space_items + ( MarkupStart, StringQuotedStart, SchemeStart, Length, EqualSign, ) def update_state(self, state, token): if isinstance(token, EqualSign): state.replace(ParseTempoAfterEqualSign()) class ParseTempoAfterEqualSign(FallthroughParser): items = space_items + ( IntegerValue, TempoSeparator, ) class ParseDuration(FallthroughParser): items = space_items + ( Dot, ) def fallthrough(self, state): state.replace(ParseDurationScaling()) class ParseDurationScaling(ParseDuration): items = space_items + ( Scaling, ) def fallthrough(self, state): state.leave() class ParseOverride(ParseLilyPond): argcount = 0 items = ( ContextName, DotPath, GrobName, GrobProperty, EqualSign, ) + base_items def update_state(self, state, token): if isinstance(token, EqualSign): state.replace(ParseDecimalValue()) class ParseRevert(FallthroughParser): r"""parse the arguments of ``\revert``""" # allow both the old scheme syntax but also the dotted 2.18+ syntax # allow either a dot between the GrobName and the property path or not # correctly fall through when one property path has been parsed # (uses ParseGrobPropertyPath and ExpectGrobProperty) # (When the old scheme syntax is used this parser also falls through, # assuming that the previous parser will handle it) items = space_items + ( ContextName, DotPath, GrobName, GrobProperty, ) def update_state(self, state, token): if isinstance(token, GrobProperty): state.replace(ParseGrobPropertyPath()) class ParseGrobPropertyPath(FallthroughParser): items = space_items + ( DotPath, ) def update_state(self, state, token): if isinstance(token, DotPath): state.enter(ExpectGrobProperty()) class ExpectGrobProperty(FallthroughParser): items = space_items + ( GrobProperty, ) def update_state(self, state, token): if isinstance(token, GrobProperty): state.leave() class ParseSet(ParseLilyPond): argcount = 0 items = ( ContextName, DotPath, ContextProperty, EqualSign, Name, ) + base_items def update_state(self, state, token): if isinstance(token, EqualSign): state.replace(ParseDecimalValue()) class ParseUnset(FallthroughParser): items = space_items + ( ContextName, DotPath, ContextProperty, Name, ) def update_state(self, state, token): if isinstance(token, ContextProperty) or token[:1].islower(): state.leave() class ParseTweak(FallthroughParser): items = space_items + ( GrobName, DotPath, GrobProperty, ) def update_state(self, state, token): if isinstance(token, GrobProperty): state.replace(ParseTweakGrobProperty()) class ParseTweakGrobProperty(FallthroughParser): items = space_items + ( DotPath, DecimalValue, ) def update_state(self, state, token): if isinstance(token, DotPath): state.enter(ExpectGrobProperty()) elif isinstance(token, DecimalValue): state.leave() class ParseTranslator(FallthroughParser): items = space_items + ( ContextName, Name, ) def update_state(self, state, token): if isinstance(token, (Name, ContextName)): state.replace(ExpectTranslatorId()) class ExpectTranslatorId(FallthroughParser): items = space_items + ( EqualSign, ) def update_state(self, state, token): if token == '=': state.replace(ParseTranslatorId()) class ParseTranslatorId(FallthroughParser): argcount = 1 items = space_items + ( Name, StringQuotedStart, ) def update_state(self, state, token): if isinstance(token, Name): state.leave() class ParseClef(FallthroughParser): argcount = 1 items = space_items + ( ClefSpecifier, StringQuotedStart, ) class ParseHideOmit(FallthroughParser): items = space_items + ( ContextName, DotPath, GrobName, ) def update_state(self, state, token): if isinstance(token, GrobName): state.leave() class ParseAccidentalStyle(FallthroughParser): items = space_items + ( ContextName, DotPath, AccidentalStyleSpecifier, ) def update_state(self, state, token): if isinstance(token, AccidentalStyleSpecifier): state.leave() class ParseAlterBroken(FallthroughParser): items = space_items + ( GrobProperty, ) def update_state(self, state, token): if isinstance(token, GrobProperty): state.replace(ParseGrobPropertyPath()) class ParseScriptAbbreviationOrFingering(FallthroughParser): argcount = 1 items = space_items + ( ScriptAbbreviation, Fingering, ) class ParseInputMode(ParseLilyPond): """Base class for parser for mode-changing music commands.""" @classmethod def update_state(cls, state, token): if isinstance(token, (OpenSimultaneous, OpenBracket)): state.enter(cls()) class ParseLyricMode(ParseInputMode): r"""Parser for ``\lyrics``, ``\lyricmode``, ``\addlyrics``, etc.""" items = base_items + ( CloseBracket, CloseSimultaneous, OpenBracket, OpenSimultaneous, PipeSymbol, LyricHyphen, LyricExtender, LyricSkip, LyricText, Dynamic, Skip, Length, MarkupStart, MarkupLines, MarkupList, ) + command_items class ExpectLyricMode(ExpectMusicList): replace = ParseLyricMode items = space_items + ( OpenBracket, OpenSimultaneous, SchemeStart, StringQuotedStart, Name, SimultaneousOrSequentialCommand, ) class ParseChordMode(ParseInputMode, ParseMusic): r"""Parser for ``\chords`` and ``\chordmode``.""" items = ( OpenBracket, OpenSimultaneous, ) + music_items + ( # TODO: specify items exactly, e.g. < > is not allowed ChordSeparator, ) def update_state(self, state, token): if isinstance(token, ChordSeparator): state.enter(ParseChordItems()) else: super(ParseChordMode, self).update_state(state, token) class ExpectChordMode(ExpectMusicList): replace = ParseChordMode class ParseNoteMode(ParseMusic): r"""Parser for ``\notes`` and ``\notemode``. Same as Music itself.""" class ExpectNoteMode(ExpectMusicList): replace = ParseNoteMode class ParseDrumChord(ParseMusic): """LilyPond inside chords in drummode ``< >``""" items = base_items + ( ErrorInChord, DrumChordEnd, Dynamic, Skip, Spacer, Q, Rest, DrumNote, Fraction, Length, PipeSymbol, VoiceSeparator, SequentialStart, SequentialEnd, SimultaneousStart, SimultaneousEnd, ChordStart, ContextName, GrobName, SlurStart, SlurEnd, PhrasingSlurStart, PhrasingSlurEnd, Tie, BeamStart, BeamEnd, LigatureStart, LigatureEnd, Direction, StringNumber, IntegerValue, ) + command_items class ParseDrumMode(ParseInputMode, ParseMusic): r"""Parser for ``\drums`` and ``\drummode``.""" items = ( OpenBracket, OpenSimultaneous, ) + base_items + ( Dynamic, Skip, Spacer, Q, Rest, DrumNote, Fraction, Length, PipeSymbol, VoiceSeparator, SequentialStart, SequentialEnd, SimultaneousStart, SimultaneousEnd, DrumChordStart, ContextName, GrobName, SlurStart, SlurEnd, PhrasingSlurStart, PhrasingSlurEnd, Tie, BeamStart, BeamEnd, LigatureStart, LigatureEnd, Direction, StringNumber, IntegerValue, ) + command_items class ExpectDrumMode(ExpectMusicList): replace = ParseDrumMode class ParseFigureMode(ParseInputMode, ParseMusic): r"""Parser for ``\figures`` and ``\figuremode``.""" items = base_items + ( CloseBracket, CloseSimultaneous, OpenBracket, OpenSimultaneous, PipeSymbol, FigureStart, Skip, Spacer, Rest, Length, ) + command_items class ParseFigure(Parser): """Parse inside ``< >`` in figure mode.""" items = base_items + ( FigureEnd, FigureBracket, FigureStep, FigureAccidental, FigureModifier, MarkupStart, MarkupLines, MarkupList, ) class ExpectFigureMode(ExpectMusicList): replace = ParseFigureMode class ParsePitchCommand(FallthroughParser): argcount = 1 items = space_items + ( Note, Octave, ) def update_state(self, state, token): if isinstance(token, Note): self.argcount -= 1 elif isinstance(token, _token.Space) and self.argcount <= 0: state.leave() class ParseTremolo(FallthroughParser): items = (TremoloDuration,) class ParseChordItems(FallthroughParser): items = ( ChordSeparator, ChordModifier, ChordStepNumber, DotChord, Note, ) class ParseDecimalValue(FallthroughParser): """Parses a decimal value without a # before it (if present).""" items = space_items + ( Fraction, DecimalValue, ) python-ly-0.9.3/ly/lex/html.py0000644000175000017500000001137512461001227017364 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Parses and tokenizes HTML input, recognizing LilyPond in HTML. """ from __future__ import unicode_literals from . import _token from . import Parser, FallthroughParser from . import lilypond class Comment(_token.Comment): pass class CommentStart(Comment, _token.BlockCommentStart): rx = r"" class String(_token.String): pass class Tag(_token.Token): pass class TagStart(Tag): rx = r"" class AttrName(_token.Token): rx = r"\w+([-_:]\w+)?" class EqualSign(_token.Token): rx = "=" def update_state(self, state): state.enter(ParseValue()) class Value(_token.Leaver): rx = r"\w+" class StringDQStart(String, _token.StringStart): rx = r'"' def update_state(self, state): state.enter(ParseStringDQ()) class StringSQStart(String, _token.StringStart): rx = r"'" def update_state(self, state): state.enter(ParseStringSQ()) class StringDQEnd(String, _token.StringEnd, _token.Leaver): rx = r'"' class StringSQEnd(String, _token.StringEnd, _token.Leaver): rx = r"'" class EntityRef(_token.Character): rx = r"\&(#\d+|#[xX][0-9A-Fa-f]+|[A-Za-z_:][\w.:_-]*);" class LilyPondTag(Tag): pass class LilyPondVersionTag(LilyPondTag): rx = r"" class LilyPondFileTag(LilyPondTag): rx = r"" class LilyPondInlineTag(LilyPondTag): rx = r"" class LilyPondTagEnd(LilyPondTag): rx = r">" def update_state(self, state): state.replace(ParseLilyPond()) class LilyPondInlineTagEnd(LilyPondTag, _token.Leaver): rx = r"/?>" class SemiColon(_token.Token): rx = r":" def update_state(self, state): state.replace(ParseLilyPondInline()) # Parsers: class ParseHTML(Parser): mode = "html" items = ( _token.Space, LilyPondVersionTag, LilyPondFileTag, LilyPondInlineTag, CommentStart, TagStart, EntityRef, ) class ParseAttr(Parser): items = ( _token.Space, TagEnd, AttrName, EqualSign, StringDQStart, StringSQStart, ) class ParseStringDQ(Parser): default = String items = ( StringDQEnd, EntityRef, ) class ParseStringSQ(Parser): default = String items = ( StringSQEnd, EntityRef, ) class ParseComment(Parser): default = Comment items = ( CommentEnd, ) class ParseValue(FallthroughParser): """Finds a value or drops back.""" items = ( _token.Space, Value, ) def fallthrough(self, state): state.leave() class ParseLilyPondAttr(Parser): items = ( _token.Space, AttrName, EqualSign, StringDQStart, StringSQStart, LilyPondTagEnd, SemiColon, ) class ParseLilyPondFileOptions(Parser): items = ( _token.Space, AttrName, EqualSign, StringDQStart, StringSQStart, LilyPondFileTagEnd, ) class ParseLilyPond(lilypond.ParseGlobal): items = ( LilyPondCloseTag, ) + lilypond.ParseGlobal.items class ParseLilyPondInline(lilypond.ParseMusic): items = ( LilyPondInlineTagEnd, ) + lilypond.ParseMusic.items python-ly-0.9.3/ly/lex/_mode.py0000644000175000017500000000703012461001227017474 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Registry for the different modes used by the tokenizer. There are two items in this module: 1. the modes dictionary. This maps a mode name to a function returning the base parser class for that mode. (This way the corresponding module only needs to be imported when the mode is really needed.) 2. the guessMode function. This tries to guess the type of the given text and returns a mode name. You can easily add more modes in separate modules and mention them here, Don't use this module directly! modes and guessMode are imported in the main tokenize module. """ from __future__ import unicode_literals __all__ = ['modes', 'guessMode'] def _modes(): """Returns a dictionary mapping a mode name to a function. The function should return the initial Parser instance for that mode. """ def lilypond(): from . import lilypond return lilypond.ParseGlobal def scheme(): from . import scheme return scheme.ParseScheme def docbook(): from . import docbook return docbook.ParseDocBook def latex(): from . import latex return latex.ParseLaTeX def texinfo(): from . import texinfo return texinfo.ParseTexinfo def html(): from . import html return html.ParseHTML # more modes can be added here return locals() # dictionary mapping mode name to a function returning initial parser instance # for that mode. Can also be used to test the existence of a mode modes = _modes() del _modes def guessMode(text): """Tries to guess the type of the input text, using a quite fast heuristic. Returns one of the strings also present as key in the modes dictionary. """ text = text.lstrip() if text.startswith(('%', '\\')): if '\\version' in text or '\\relative' in text or '\\score' in text: return "lilypond" if "\\documentclass" in text or "\\begin{document}" in text: return "latex" return "lilypond" if text.startswith("<<"): return "lilypond" if text.startswith("<"): if 'DOCTYPE book' in text or "'.format(module, name, contents, where) class patternproperty(object): """Property that caches the return value of its function and returns that next time. Use this if the rx attribute (the pattern string to match tokens for) of a Token subclass is already costly to create and you want it created lazily (i.e. only when parsing starts): @patternproperty def rx(): ...complicated function returning the regular expression string... """ def __init__(self, func): self.func = func def __get__(self, instance, owner): try: return self.rx except AttributeError: self.rx = self.func() return self.rx class Unparsed(Token): """Represents an unparsed piece of input text.""" # some token types with special behaviour: class Item(Token): """A token that decreases the argument count of the current parser.""" def update_state(self, state): state.endArgument() class Leaver(Token): """A token that leaves the current parser.""" def update_state(self, state): state.leave() # some generic types: class Space(Token): """A token containing whitespace.""" rx = r'\s+' class Newline(Space): """A token that is a single newline.""" rx = r'\n' # some base types that should be inherited: class Comment(Token): """Base class for tokens that belong to a comment.""" class LineComment(Comment): """Base class for items that are a whole line comment.""" class BlockComment(Comment): """Base class for tokens that belong to a block/multiline comment.""" class BlockCommentStart(BlockComment): """Base class for tokens that start a block/multiline comment.""" class BlockCommentEnd(BlockComment): """Base class for tokens that end a block/multiline comment.""" class String(Token): """Base class for tokens that belong to a quote-delimited string.""" class StringStart(String): """Base class for tokens that start a quote-delimited string.""" class StringEnd(String): """Base class for tokens that end a quote-delimited string.""" class Character(Token): """Base class for tokens that are an (escaped) character.""" class Numeric(Token): """Base class for tokens that are a numerical value.""" class Error(Token): """Base class for tokens that represent erroneous input.""" # some mixin classes that make special handling of tokens possible besides correct parsing: # MatchEnd and MatchStart can be used by parenthesis/brace matcher. # the matchname class attribute defines the type, so that it is independent # of the way other types could be nested. class MatchStart(object): """Mixin class for tokens that have a matching token forward in the text. The matchname attribute should give a unique name. """ matchname = "" class MatchEnd(object): """Mixin class for tokens that have a matching token backward in the text. The matchname attribute should give a unique name. """ matchname = "" # Indent and Dedent can be used by an (auto) indenter. class Indent(object): """Mixin class for tokens that have the text on the next line indent more.""" class Dedent(object): """Mixin class for tokens that have the text on the next line indent less.""" python-ly-0.9.3/ly/lex/scheme.py0000644000175000017500000001121312461001227017653 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Parses and tokenizes Scheme input. """ from __future__ import unicode_literals from . import _token from . import Parser, FallthroughParser class Scheme(_token.Token): """Baseclass for Scheme tokens.""" pass class String(_token.String): pass class StringQuotedStart(String, _token.StringStart): rx = r'"' def update_state(self, state): state.enter(ParseString()) class StringQuotedEnd(String, _token.StringEnd): rx = r'"' def update_state(self, state): state.leave() state.endArgument() class StringQuoteEscape(_token.Character): rx = r'\\[\\"]' class Comment(_token.Comment): pass class LineComment(Comment, _token.LineComment): rx = r";.*$" class BlockCommentStart(Comment, _token.BlockCommentStart): rx = r"#!" def update_state(self, state): state.enter(ParseBlockComment()) class BlockCommentEnd(Comment, _token.BlockCommentEnd, _token.Leaver): rx = "!#" class BlockComment(Comment, _token.BlockComment): pass class OpenParen(Scheme, _token.MatchStart, _token.Indent): rx = r"\(" matchname = "schemeparen" def update_state(self, state): state.enter(ParseScheme()) class CloseParen(Scheme, _token.MatchEnd, _token.Dedent): rx = r"\)" matchname = "schemeparen" def update_state(self, state): state.leave() state.endArgument() class Quote(Scheme): rx = r"['`,]" class Dot(Scheme): rx = r"\.(?!\S)" class Bool(Scheme, _token.Item): rx = r"#[tf]\b" class Char(Scheme, _token.Item): rx = r"#\\([a-z]+|.)" class Word(Scheme, _token.Item): rx = r'[^()"{}\s]+' class Keyword(Word): @classmethod def test_match(cls, match): from .. import data return match.group() in data.scheme_keywords() class Function(Word): @classmethod def test_match(cls, match): from .. import data return match.group() in data.scheme_functions() class Variable(Word): @classmethod def test_match(cls, match): from .. import data return match.group() in data.scheme_variables() class Constant(Word): @classmethod def test_match(cls, match): from .. import data return match.group() in data.scheme_constants() class Number(_token.Item, _token.Numeric): rx = (r"(" r"-?\d+|" r"#(b[0-1]+|o[0-7]+|x[0-9a-fA-F]+)|" r"[-+]inf.0|[-+]?nan.0" r")(?=$|[)\s])") class Fraction(Number): rx = r"-?\d+/\d+(?=$|[)\s])" class Float(Number): rx = r"-?((\d+(\.\d*)|\.\d+)(E\d+)?)(?=$|[)\s])" class VectorStart(OpenParen): rx = r"#\(" class LilyPond(_token.Token): pass class LilyPondStart(LilyPond, _token.MatchStart, _token.Indent): rx = r"#{" matchname = "schemelily" def update_state(self, state): state.enter(ParseLilyPond()) class LilyPondEnd(LilyPond, _token.Leaver, _token.MatchEnd, _token.Dedent): rx = r"#}" matchname = "schemelily" # Parsers class ParseScheme(Parser): mode = 'scheme' items = ( _token.Space, OpenParen, CloseParen, LineComment, BlockCommentStart, LilyPondStart, VectorStart, Dot, Bool, Char, Quote, Fraction, Float, Number, Constant, Keyword, Function, Variable, Word, StringQuotedStart, ) class ParseString(Parser): default = String items = ( StringQuotedEnd, StringQuoteEscape, ) class ParseBlockComment(Parser): default = BlockComment items = ( BlockCommentEnd, ) from . import lilypond class ParseLilyPond(lilypond.ParseMusic): items = (LilyPondEnd,) + lilypond.ParseMusic.items python-ly-0.9.3/ly/lex/docbook.py0000644000175000017500000000216412461001227020034 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Parses and tokenizes DocBook input, recognizing LilyPond in DocBook. """ from __future__ import unicode_literals from . import _token from . import Parser class ParseDocBook(Parser): mode = "docbook" items = ( _token.Space, ) python-ly-0.9.3/ly/lex/texinfo.py0000644000175000017500000001104112461001227020062 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Parses and tokenizes Texinfo input, recognizing LilyPond in Texinfo. """ from __future__ import unicode_literals from . import _token from . import Parser, FallthroughParser class Comment(_token.Comment): pass class LineComment(Comment, _token.LineComment): rx = r"@c\b.*$" class BlockCommentStart(Comment, _token.BlockCommentStart): rx = r"@ignore\b" def update_state(self, state): state.enter(ParseComment()) class BlockCommentEnd(Comment, _token.Leaver, _token.BlockCommentEnd): rx = r"@end\s+ignore\b" class Attribute(_token.Token): pass class Keyword(_token.Token): rx = r"@[a-zA-Z]+" class Block(_token.Token): pass class BlockStart(Block): rx = r"@[a-zA-Z]+\{" def update_state(self, state): state.enter(ParseBlock()) class BlockEnd(Block, _token.Leaver): rx = r"\}" class EscapeChar(_token.Character): rx = r"@[@{}]" class Accent(EscapeChar): rx = "@['\"',=^`~](\\{[a-zA-Z]\\}|[a-zA-Z]\\b)" class Verbatim(_token.Token): pass class VerbatimStart(Keyword): rx = r"@verbatim\b" def update_state(self, state): state.enter(ParseVerbatim()) class VerbatimEnd(Keyword, _token.Leaver): rx = r"@end\s+verbatim\b" class LilyPondBlockStart(Block): rx = r"@lilypond(?=(\[[a-zA-Z,=0-9\\\s]+\])?\{)" def update_state(self, state): state.enter(ParseLilyPondBlockAttr()) class LilyPondBlockStartBrace(Block): rx = r"\{" def update_state(self, state): state.replace(ParseLilyPondBlock()) class LilyPondBlockEnd(Block, _token.Leaver): rx = r"\}" class LilyPondEnvStart(Keyword): rx = r"@lilypond\b" def update_state(self, state): state.enter(ParseLilyPondEnvAttr()) class LilyPondEnvEnd(Keyword, _token.Leaver): rx = r"@end\s+lilypond\b" class LilyPondFileStart(Block): rx = r"@lilypondfile\b" def update_state(self, state): state.enter(ParseLilyPondFile()) class LilyPondFileStartBrace(Block): rx = r"\{" def update_state(self, state): state.replace(ParseBlock()) class LilyPondAttrStart(Attribute): rx = r"\[" def update_state(self, state): state.enter(ParseLilyPondAttr()) class LilyPondAttrEnd(Attribute, _token.Leaver): rx = r"\]" # Parsers: class ParseTexinfo(Parser): mode = "texinfo" items = ( LineComment, BlockCommentStart, Accent, EscapeChar, LilyPondBlockStart, LilyPondEnvStart, LilyPondFileStart, BlockStart, VerbatimStart, Keyword, ) class ParseComment(Parser): default = Comment items = ( BlockCommentEnd, ) class ParseBlock(Parser): items = ( BlockEnd, Accent, EscapeChar, BlockStart, Keyword, ) class ParseVerbatim(Parser): default = Verbatim items = ( VerbatimEnd, ) class ParseLilyPondBlockAttr(Parser): items = ( LilyPondAttrStart, LilyPondBlockStartBrace, ) class ParseLilyPondEnvAttr(FallthroughParser): items = ( LilyPondAttrStart, ) def fallthrough(self, state): state.replace(ParseLilyPondEnv()) class ParseLilyPondAttr(Parser): default = Attribute items = ( LilyPondAttrEnd, ) class ParseLilyPondFile(Parser): items = ( LilyPondAttrStart, LilyPondFileStartBrace, ) from . import lilypond class ParseLilyPondBlock(lilypond.ParseGlobal): items = ( LilyPondBlockEnd, ) + lilypond.ParseGlobal.items class ParseLilyPondEnv(lilypond.ParseGlobal): items = ( LilyPondEnvEnd, ) + lilypond.ParseGlobal.items python-ly-0.9.3/ly/lex/latex.py0000644000175000017500000000215512461001227017531 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Parses and tokenizes LaTeX input, recognizing LilyPond in LaTeX. """ from __future__ import unicode_literals from . import _token from . import Parser class ParseLaTeX(Parser): mode = "latex" items = ( _token.Space, ) python-ly-0.9.3/ly/pkginfo.py0000644000175000017500000000314012601317066017263 0ustar wilbertwilbert00000000000000# 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. """ Meta-information about the LY package. This information is used by the install script, and also for the command ``ly --version``. """ from __future__ import unicode_literals #: name of the package name = "python-ly" #: the current version version = "0.9.3" #: short description description = "Tool and library for manipulating LilyPond files" #: long description long_description = \ "The python-ly package provides a Python library and a commandline tool " \ "that can be used to parse and manipulate LilyPond source files." #: maintainer name maintainer = "Wilbert Berendsen" #: maintainer email maintainer_email = "info@frescobaldi.org" #: homepage url = "https://github.com/wbsoft/python-ly" #: license license = "GPL" python-ly-0.9.3/ly/etreeutil.py0000644000175000017500000000327112461001227017626 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2013 - 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. """ Utility functions for use with xml.etree.ElementTree. """ from __future__ import unicode_literals def isblank(s): """Return True if s is empty or whitespace only.""" return not s or s.isspace() def indent(elem, indent_string=" ", level=0): """Indent the XML in element. Text content that is already non-whitespace is not changed. """ # based on http://effbot.org/zone/element-lib.htm#prettyprint i = "\n" + indent_string * level if len(elem): if isblank(elem.text): elem.text = i + indent_string if isblank(elem.tail): elem.tail = i for elem in elem: indent(elem, indent_string, level+1) if isblank(elem.tail): elem.tail = i else: if level and isblank(elem.tail): elem.tail = i python-ly-0.9.3/ly/slexer.py0000644000175000017500000004417612476157645017165 0ustar wilbertwilbert00000000000000# === Python slexer (Stateful Lexer) module === # # Copyright (c) 2008 - 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. r""" slexer -- Stateful Lexer ======================== parses text, searching for tokens represented by a regular expression. Only depends on the standard Python re module. You need to create at least one subclass of Parser, and a subclass of Token for every type of text to search for. Then you list the token class names in the 'items' tuple of the Parser subclass definition. Different contexts can be parsed by creating multiple Parser subclasses. A Parser searches for tokens using the list of Token classes. (Token is simply a subclass of str in Python 3 and of unicode in Python 2). Every Token subclass has the regular expression part to search for in its 'rx' class attribute. You start parsing text by instantiating a State (you don't need to subclass that) with the Parser subclass you want to parse the text with. Then you iterate over the generated tokens using the tokens(text) method of the State instance. You can use the tokens just as strings (e.g. if token == 'text'...) but you can also test for the type of the token (e.g. if isinstance(token, Number)...). The tokens also carry a 'pos' and an 'end' attribute, specifying their position in the parsed text string. A token may cause a different Parser to be entered, of the current Parser to be left, etc. This is done by implementing the update_state() method of the Token subclass. This method is called automatically when the Token is instantiated. The State maintains the parsing state (the list of active Parser instances). A State can be frozen to be thawed later to resume parsing text starting in a particular context. A Fridge can be used to store and recover a state under a simple integer number. How to use slexer:: from slexer import Token, Parser, State # create token classes: class Word(Token): rx = r'\w+' class Number(Token): rx = r'\d+' class String(Token): pass class StringStart(String): rx = '"' def update_state(self, state): state.enter(PString()) class StringEnd(String): rx = '"' def update_state(self, state): state.leave() # create parsers: class PTest(Parser): '''Looks for numbers, words and the double quote.''' items = ( Number, Word, StringStart, ) class PString(Parser): '''Returns String by default, quits at double quote.''' default = String items = ( StringEnd, ) s = State(PTest) for t in s.tokens( 'een tekst met 7 woorden, ' 'een "tekst met 2 aanhalingstekens" ' 'en 2 of 3 nummers'): print(t.__class__, t) Running the above code, the result is:: een tekst met 7 woorden een " tekst met 2 aanhalingstekens " en 2 of 3 nummers """ from __future__ import unicode_literals from __future__ import print_function try: str = unicode except NameError: pass import re __all__ = ['Token', 'Parser', 'FallthroughParser', 'State', 'Fridge'] class State(object): """Maintains state while parsing text. You instantiate a State object with an initial parser class. Then you use tokens(text) to start parsing for tokens. The state is basically a list of Parser instances; the last one is the active one. The enter() and leave() methods respectively enter a new parser or leave the current parser. You can't leave() the initial parser instance. """ def __init__(self, initialParserClass): """Construct the State with an initial Parser instance.""" self.state = [initialParserClass()] def parser(self): """Return the currently active Parser instance.""" return self.state[-1] def parsers(self): """Return all active parsers, the most current one first.""" return self.state[::-1] def tokens(self, text, pos=0): """Parse a text string using our state info. Yields Token instances. All tokens are a subclass of str (or unicode in Python 2.x) and have a pos and an end attribute, describing their position in the original string. If the current parser defines a 'default' class attribute, it is the Token subclass to use for pieces of text that would otherwise be skipped. """ while True: parser = self.parser() m = parser.parse(text, pos) if m: if parser.default and pos < m.start(): token = parser.default(text[pos:m.start()], pos) token.update_state(self) yield token token = parser.token(m) token.update_state(self) yield token pos = m.end() elif pos == len(text) or parser.fallthrough(self): break if parser.default and pos < len(text): token = parser.default(text[pos:], pos) token.update_state(self) yield token def enter(self, parser): """Enter a new parser. Note: 'parser' is an instantiated Parser subclass. Most times this method will be called from with the update_state() method of a Token subclass (or from a Parser subclass, which is also possible: the default implementation of Token.update_state() calls Parser.update_state(), which does nothing by default). E.g. in the Token subclass:: def update_state(self, state): state.enter(SomeDifferentParser()) """ self.state.append(parser) def leave(self): """Leave the current parser and pop back to the previous. The first parser (specified on instantiation) will never be left. """ if len(self.state) > 1: self.state.pop() def replace(self, parser): """Replace the current parser with a new one. Somewhat equivalent to:: state.leave() state.enter(SomeDifferentParser) But using this method you can also replace the first parser. """ self.state[-1] = parser def depth(self): """Return the number of parsers currently active (1 or more). You can use this e.g. to keep parsing until some context ends:: tokens = state.tokens(text) # iterator depth = state.depth() for token in tokens: if state.depth() < depth: break # do something """ return len(self.state) def follow(self, token): """Act as if the token has been instantiated with the current state. You need this when you already have the parsed tokens, (e.g. cached or saved somehow) but want to know which parser created them. This method changes state according to the token. Basically it calls the update_state() method of the token instance, but it does some more work behind the scenes to ensure that the FallthroughParser type (see below) also is handled correctly. """ while self.parser()._follow(token, self): pass token.update_state(self) def freeze(self): """Return the current state as a tuple (hashable object).""" return tuple((p.__class__, p.freeze()) for p in self.state) @classmethod def thaw(cls, frozen): """Reproduce a State object from the frozen state argument.""" state = cls.__new__(cls) state.state = [cls.thaw(attrs) for cls, attrs in frozen] return state class Token(str): """Represents a parsed piece of text. The subclass determines the type. You should put the regular expression string in the rx class attribute. In the rx string, you may not use named groups starting with "g\\_". To add token types to a Parser class, list the token class in the items attribute of the Parser class. """ __slots__ = ['pos', 'end'] rx = None @classmethod def test_match(cls, match): """Should return True if the match should indeed instantiate this class. This class method is only called if multiple Token classes in the Parser's items list have the same rx attribute. This method is then called for every matching Token subclass until one returns True. That token is then instantiated. (The method is not called for the last class in the list that have the same rx attribute, and also not if there is only one class with that rx attribute.) The default implementation always returns True. """ return True def __new__(cls, string, pos): token = str.__new__(cls, string) token.pos = pos token.end = pos + len(token) return token def update_state(self, state): """Lets the token update the state, e.g. enter a different parser. This method is called by the State upon instantiation of the tokens. Don't use it later on to have a State follow already instantiated Tokens because the FallthroughParser type can also change the state without generating a Token. Use State.follow() to have a State follow instantiated Tokens. The default implementation lets the Parser decide on state change. """ state.parser().update_state(state, self) class PatternProperty(object): """A descriptor that lazily generates a regular expression. The expression is based on the list of Token subclasses in the items attribute of a Parser class. Also creates an index attribute for the parser class that maps the lastindex property of a match object to the token class. When the pattern is requested for the first time, it is created and also written in the class, overwriting this descriptor. """ def __get__(self, instance, owner): try: owner.pattern = self.pattern owner.index = self.index except AttributeError: # if Token classes have the same regexp string, group them patterns = [] counter = {} for cls in uniq(owner.items): rx = cls.rx try: counter[rx].append(cls) except KeyError: counter[rx] = [cls] patterns.append(rx) # make the pattern owner.pattern = self.pattern = pattern = re.compile("|".join( "(?P{1})".format(i, rx) for i, rx in enumerate(patterns)), owner.re_flags) # make a fast mapping list from matchObj.lastindex to the token class indices = sorted(v for k, v in pattern.groupindex.items() if k.startswith('g_')) owner.index = self.index = index = [None] * (indices[-1] + 1) for i, rx in zip(indices, patterns): index[i] = counter[rx] return owner.pattern class ParserMeta(type): """Metaclass for Parser subclasses. Adds a 'pattern' attribute with a PatternProperty() when the class also defines 'items'. """ def __new__(cls, name, bases, attrd): if attrd.get('items'): attrd['pattern'] = PatternProperty() return type.__new__(cls, name, bases, attrd) class Parser(object): """Abstract base class for Parsers. When creating Parser subclasses, you should set the 'items' attribute to a tuple of Token subclasses. On class construction, a large regular expression pattern is built by combining the expressions from the 'rx' attributes of the Token subclasses. Additionally, you may implement the update_state() method which is called by the default implementation of update_state() in Token. """ re_flags = 0 # the re.compile flags to use default = None # if not None, the default class for unparsed pieces of text # tuple of Token classes to look for in text items = () def parse(self, text, pos): """Parse text from position pos and returns a Match Object or None.""" return self.pattern.search(text, pos) def token(self, match): """Return a Token instance of the correct class. The match object is returned by the parse() method. """ clss = self.index[match.lastindex] for c in clss[:-1]: if c.test_match(match): return c(match.group(), match.start()) return clss[-1](match.group(), match.start()) def _follow(self, token, state): """(Internal) Called by State.follow(). Does nothing.""" pass def freeze(self): """Return our instance values as a hashable tuple.""" return () @classmethod def thaw(cls, attrs): return cls(*attrs) def fallthrough(self, state): """Called when no match is returned by parse(). If this function returns True, the tokenizer stops parsing, assuming all text has been consumed. If this function returns False or None, it should alter the state (switch parsers) and parsing continues using the new Parser. The default implementation simply returns True. """ return True def update_state(self, state, token): """Called by the default implementation of Token.update_state(). Does nothing by default. """ pass # This syntax to make Parser use the metaclass works in both Python2 and 3 Parser = ParserMeta(Parser.__name__, Parser.__bases__, dict(Parser.__dict__)) class FallthroughParser(Parser): """Base class for parsers that 'match' instead of 'search' for a pattern. You can also implement the fallthrough() method to do something with the state if there is no match. The default is to leave the current parser. See Parser(). """ def parse(self, text, pos): """Match text at position pos and returns a Match Object or None.""" return self.pattern.match(text, pos) def _follow(self, token, state): """(Internal) Called by State.follow(). Falls through if the token can't have been generated by this parser. """ if type(token) not in self.items: self.fallthrough(state) return True def fallthrough(self, state): """Called when no match is returned by parse(). This implementation leaves the current parser and returns None (causing the State to continue parsing). """ state.leave() class Fridge(object): """Stores frozen States under an integer number.""" def __init__(self, stateClass = State): self._stateClass = stateClass self._states = [] def freeze(self, state): """Stores a state and return an identifying integer.""" frozen = state.freeze() try: return self._states.index(frozen) except ValueError: i = len(self._states) self._states.append(frozen) return i def thaw(self, num): """Returns the state stored under the specified number.""" if 0 <= num < len(self._states): return self._stateClass.thaw(self._states[num]) def count(self): """Returns the number of stored frozen states.""" return len(self._states) def uniq(iterable): """Yields unique items from iterable.""" seen, l = set(), 0 for i in iterable: seen.add(i) if len(seen) > l: yield i l = len(seen) if __name__ == "__main__": # test class Word(Token): rx = r'\w+' class Number(Token): rx = r'\d+' class String(Token): pass class StringStart(String): rx = '"' def update_state(self, state): state.enter(PString()) class StringEnd(String): rx = '"' def update_state(self, state): state.leave() class PTest(Parser): items = ( Number, Word, StringStart, ) class PString(Parser): default = String items = ( StringEnd, ) s = State(PTest) print('test:') for t in s.tokens( 'een tekst met 7 woorden, ' 'een "tekst met 2 aanhalingstekens" ' 'en 2 of 3 nummers'): print(t.__class__, t) print('test the Fridge:') s = State(PTest) f = Fridge() for t in s.tokens('text with "part of a '): print(t.__class__, t) n = f.freeze(s) # recover print('freeze and recover:') s = f.thaw(n) for t in s.tokens('quoted string" in the middle'): print(t.__class__, t) python-ly-0.9.3/ly/pitch/0000775000175000017500000000000012636745216016401 5ustar wilbertwilbert00000000000000python-ly-0.9.3/ly/pitch/__init__.py0000644000175000017500000003061712503612353020503 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Pitch manipulation. """ from __future__ import unicode_literals import re from fractions import Fraction import ly.lex.lilypond pitchInfo = { 'nederlands': ( ('c', 'd', 'e', 'f', 'g', 'a', 'b'), ('eses', 'eseh', 'es', 'eh', '', 'ih', 'is', 'isih', 'isis'), (('ees', 'es'), ('aes', 'as')) ), 'english': ( ('c', 'd', 'e', 'f', 'g', 'a', 'b'), ('ff', 'tqf', 'f', 'qf', '', 'qs', 's', 'tqs', 'ss'), ), 'deutsch': ( ('c', 'd', 'e', 'f', 'g', 'a', 'h'), ('eses', 'eseh', 'es', 'eh', '', 'ih', 'is', 'isih', 'isis'), (('ases', 'asas'), ('ees', 'es'), ('aes', 'as'), ('heses', 'heses'), ('hes', 'b')) ), 'svenska': ( ('c', 'd', 'e', 'f', 'g', 'a', 'h'), ('essess', '', 'ess', '', '', '', 'iss', '', 'ississ'), (('ees', 'es'), ('aes', 'as'), ('hessess', 'hessess'), ('hess', 'b')) ), 'italiano': ( ('do', 're', 'mi', 'fa', 'sol', 'la', 'si'), ('bb', 'bsb', 'b', 'sb', '', 'sd', 'd', 'dsd', 'dd') ), 'espanol': ( ('do', 're', 'mi', 'fa', 'sol', 'la', 'si'), ('bb', '', 'b', '', '', '', 's', '', 'ss') ), 'portugues': ( ('do', 're', 'mi', 'fa', 'sol', 'la', 'si'), ('bb', 'btqt', 'b', 'bqt', '', 'sqt', 's', 'stqt', 'ss') ), 'vlaams': ( ('do', 're', 'mi', 'fa', 'sol', 'la', 'si'), ('bb', '', 'b', '', '', '', 'k', '', 'kk') ), } pitchInfo['norsk'] = pitchInfo['deutsch'] pitchInfo['suomi'] = pitchInfo['deutsch'] pitchInfo['catalan'] = pitchInfo['italiano'] class PitchNameNotAvailable(Exception): """Exception raised when there is no name for a pitch. Can occur when translating pitch names, if the target language e.g. does not have quarter-tone names. """ def __init__(self, language): super(PitchNameNotAvailable, self).__init__() self.language = language class Pitch(object): """A pitch with note, alter and octave attributes. Attributes may be manipulated directly. """ def __init__(self, note=0, alter=0, octave=0, accidental="", octavecheck=None): self.note = note # base note (c, d, e, f, g, a, b) # as integer (0 to 6) self.alter = alter # # = .5; b = -.5; natural = 0 self.octave = octave # '' = 2; ,, = -2 self.accidental = accidental # "", "?" or "!" self.octavecheck = octavecheck # a number is an octave check def __repr__(self): return ''.format(self.output()) def output(self, language="nederlands"): """Returns our string representation.""" res = [] res.append(pitchWriter(language)(self.note, self.alter)) res.append(octaveToString(self.octave)) res.append(self.accidental) if self.octavecheck is not None: res.append('=') res.append(octaveToString(self.octavecheck)) return ''.join(res) @classmethod def c1(cls): """Returns a pitch c'.""" return cls(octave=1) @classmethod def c0(cls): """Returns a pitch c.""" return cls() @classmethod def f0(cls): """Return a pitch f.""" return cls(3) def copy(self): """Returns a new instance with our attributes.""" return self.__class__(self.note, self.alter, self.octave) def makeAbsolute(self, lastPitch): """Makes ourselves absolute, i.e. sets our octave from lastPitch.""" self.octave += lastPitch.octave - (self.note - lastPitch.note + 3) // 7 def makeRelative(self, lastPitch): """Makes ourselves relative, i.e. changes our octave from lastPitch.""" self.octave -= lastPitch.octave - (self.note - lastPitch.note + 3) // 7 class PitchWriter(object): language = "unknown" def __init__(self, names, accs, replacements=()): self.names = names self.accs = accs self.replacements = replacements def __call__(self, note, alter = 0): """ Returns a string representing the pitch in our language. Raises PitchNameNotAvailable if the requested pitch has an alteration that is not available in the current language. """ pitch = self.names[note] if alter: acc = self.accs[int(alter * 4 + 4)] if not acc: raise PitchNameNotAvailable(self.language) pitch += acc for s, r in self.replacements: if pitch.startswith(s): pitch = r + pitch[len(s):] break return pitch class PitchReader(object): def __init__(self, names, accs, replacements=()): self.names = list(names) self.accs = list(accs) self.replacements = replacements self.rx = re.compile("({0})({1})?$".format("|".join(names), "|".join(acc for acc in accs if acc))) def __call__(self, text): for s, r in self.replacements: if text.startswith(r): text = s + text[len(r):] for dummy in 1, 2: m = self.rx.match(text) if m: note = self.names.index(m.group(1)) if m.group(2): alter = Fraction(self.accs.index(m.group(2)) - 4, 4) else: alter = 0 return note, alter # HACK: were we using (rarely used) long english syntax? text = text.replace('flat', 'f').replace('sharp', 's') return False def octaveToString(octave): """Converts numeric octave to a string with apostrophes or commas. 0 -> "" ; 1 -> "'" ; -1 -> "," ; etc. """ return octave < 0 and ',' * -octave or "'" * octave def octaveToNum(octave): """Converts string octave to an integer: "" -> 0 ; "," -> -1 ; "'''" -> 3 ; etc. """ return octave.count("'") - octave.count(",") _pitchReaders = {} _pitchWriters = {} def pitchReader(language): """Returns a PitchReader for the specified language.""" try: return _pitchReaders[language] except KeyError: res = _pitchReaders[language] = PitchReader(*pitchInfo[language]) return res def pitchWriter(language): """Returns a PitchWriter for the specified language.""" try: return _pitchWriters[language] except KeyError: res = _pitchWriters[language] = PitchWriter(*pitchInfo[language]) res.language = language return res class PitchIterator(object): """Iterate over notes or pitches in a source.""" def __init__(self, source, language="nederlands"): """Initialize with a ly.document.Source. The language is by default set to "nederlands". """ self.source = source self.setLanguage(language) def setLanguage(self, lang): r"""Changes the pitch name language to use. Called internally when \language or \include tokens are encountered with a valid language name/file. Sets the language attribute to the language name and the read attribute to an instance of ly.pitch.PitchReader. """ if lang in pitchInfo.keys(): self.language = lang return True def tokens(self): """Yield all the tokens from the source, following the language.""" for t in self.source: yield t if isinstance(t, ly.lex.lilypond.Keyword): if t in ("\\include", "\\language"): for t in self.source: if not isinstance(t, ly.lex.Space) and t != '"': lang = t[:-3] if t.endswith('.ly') else t[:] if self.setLanguage(lang): yield LanguageName(lang, t.pos) break yield t def read(self, token): """Reads the token and returns (note, alter) or None.""" return pitchReader(self.language)(token) def pitches(self): """Yields all tokens, but collects Note and Octave tokens. When a Note is encountered, also reads octave and octave check and then a Pitch is yielded instead of the tokens. """ tokens = self.tokens() for t in tokens: while isinstance(t, ly.lex.lilypond.Note): p = self.read(t) if not p: break p = Pitch(*p) p.note_token = t p.octave_token = None p.accidental_token = None p.octavecheck_token = None t = None # prevent hang in this loop for t in tokens: if isinstance(t, ly.lex.lilypond.Octave): p.octave = octaveToNum(t) p.octave_token = t elif isinstance(t, ly.lex.lilypond.Accidental): p.accidental_token = p.accidental = t elif isinstance(t, ly.lex.lilypond.OctaveCheck): p.octavecheck = octaveToNum(t) p.octavecheck_token = t break elif not isinstance(t, ly.lex.Space): break yield p if t is None: break else: yield t def position(self, t): """Returns the cursor position for the given token or Pitch.""" if isinstance(t, Pitch): t = t.note_token return self.source.position(t) def write(self, pitch, language=None): """Output a changed Pitch. The Pitch is written in the Source's document. To use this method reliably, you must instantiate the PitchIterator with a ly.document.Source that has tokens_with_position set to True. """ document = self.source.document pwriter = pitchWriter(language or self.language) note = pwriter(pitch.note, pitch.alter) end = pitch.note_token.end if note != pitch.note_token: document[pitch.note_token.pos:end] = note octave = octaveToString(pitch.octave) if octave != pitch.octave_token: if pitch.octave_token is None: document[end:end] = octave else: end = pitch.octave_token.end document[pitch.octave_token.pos:end] = octave if pitch.accidental: if pitch.accidental_token is None: document[end:end] = pitch.accidental elif pitch.accidental != pitch.accidental_token: end = pitch.accidental_token.end document[pitch.accidental_token.pos:end] = pitch.accidental elif pitch.accidental_token: del document[pitch.accidental_token.pos:pitch.accidental_token.end] if pitch.octavecheck is not None: octavecheck = '=' + octaveToString(pitch.octavecheck) if pitch.octavecheck_token is None: document[end:end] = octavecheck elif octavecheck != pitch.octavecheck_token: document[pitch.octavecheck_token.pos:pitch.octavecheck_token.end] = octavecheck elif pitch.octavecheck_token: del document[pitch.octavecheck_token.pos:pitch.octavecheck_token.end] class LanguageName(ly.lex.Token): """A Token that denotes a language name.""" pass python-ly-0.9.3/ly/pitch/abs2rel.py0000644000175000017500000001234212503612647020277 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2011 - 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. """ Convert absolute music to relative music. """ from __future__ import unicode_literals import itertools import ly.lex.lilypond def abs2rel(cursor, language="nederlands"): """Converts pitches from absolute to relative.""" start = cursor.start cursor.start = 0 source = ly.document.Source(cursor, True, tokens_with_position=True) pitches = ly.pitch.PitchIterator(source, language) psource = pitches.pitches() if start > 0: # consume tokens before the selection, following the language t = source.consume(pitches.tokens(), start) if t: psource = itertools.chain((t,), psource) # this class dispatches the tokens. we can't use a generator function # as that doesn't like to be called again while there is already a body # running. class gen(object): def __iter__(self): return self def __next__(self): t = next(psource) while isinstance(t, (ly.lex.Space, ly.lex.Comment)): t = next(psource) if t == '\\relative' and isinstance(t, ly.lex.lilypond.Command): relative() t = next(psource) elif isinstance(t, ly.lex.lilypond.ChordMode): consume() # do not change chords t = next(psource) elif isinstance(t, ly.lex.lilypond.MarkupScore): consume() t = next(psource) return t next = __next__ tsource = gen() def getpitches(iterable): """Consumes iterable but only yields Pitch instances.""" for p in iterable: if isinstance(p, ly.pitch.Pitch): yield p def context(): """Consume tokens till the level drops (we exit a construct).""" depth = source.state.depth() for t in tsource: yield t if source.state.depth() < depth: return def consume(): """Consume tokens from context() returning the last token, if any.""" t = None for t in context(): pass return t def relative(): r"""Consume the whole \relative expression without doing anything. """ # skip pitch argument t = next(tsource) if isinstance(t, ly.pitch.Pitch): t = next(tsource) while True: # eat stuff like \new Staff == "bla" \new Voice \notes etc. if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator): t = consume() elif isinstance(t, ly.lex.lilypond.NoteMode): t = next(tsource) else: break if t in ('{', '<<', '<'): consume() # Do it! with cursor.document as document: for t in tsource: if t in ('{', '<<'): # Ok, parse current expression. pos = t.pos # where to insert the \relative command lastPitch = None chord = None for t in context(): # skip commands with pitches that do not count if isinstance(t, ly.lex.lilypond.PitchCommand): consume() elif isinstance(t, ly.lex.lilypond.ChordStart): # Handle chord chord = [] elif isinstance(t, ly.lex.lilypond.ChordEnd): if chord: lastPitch = chord[0] chord = None elif isinstance(t, ly.pitch.Pitch): # Handle pitch if lastPitch is None: lastPitch = ly.pitch.Pitch.c1() lastPitch.octave = t.octave if t.note > 3: lastPitch.octave += 1 document[pos:pos] = "\\relative {0} ".format( lastPitch.output(pitches.language)) p = t.copy() t.makeRelative(lastPitch) pitches.write(t) lastPitch = p # remember the first pitch of a chord if chord == []: chord.append(p) python-ly-0.9.3/ly/pitch/transpose.py0000644000175000017500000003367312503612571020771 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Transposing music. """ from __future__ import unicode_literals from fractions import Fraction import ly.lex.lilypond class Transposer(object): """Transpose pitches. Instantiate with a from- and to-Pitch, and optionally a scale. The scale is a list with the pitch height of the unaltered step (0 .. 6). The default scale is the normal scale: C, D, E, F, G, A, B. """ scale = (0, 1, 2, Fraction(5, 2), Fraction(7, 2), Fraction(9, 2), Fraction(11, 2)) def __init__(self, fromPitch, toPitch, scale = None): if scale is not None: self.scale = scale # the number of octaves we need to transpose self.octave = toPitch.octave - fromPitch.octave # the number of base note steps (c->d == 1, e->f == 1, etc.) self.steps = toPitch.note - fromPitch.note # the number (fraction) of real whole steps self.alter = (self.scale[toPitch.note] + toPitch.alter - self.scale[fromPitch.note] - fromPitch.alter) def transpose(self, pitch): doct, note = divmod(pitch.note + self.steps, 7) pitch.alter += self.alter - doct * 6 - self.scale[note] + self.scale[pitch.note] pitch.octave += self.octave + doct pitch.note = note # change the step if alterations fall outside -1 .. 1 while pitch.alter > 1: doct, note = divmod(pitch.note + 1, 7) pitch.alter -= doct * 6 + self.scale[note] - self.scale[pitch.note] pitch.octave += doct pitch.note = note while pitch.alter < -1: doct, note = divmod(pitch.note - 1, 7) pitch.alter += doct * -6 + self.scale[pitch.note] - self.scale[note] pitch.octave += doct pitch.note = note class ModeShifter(Transposer): """ Shift pitches to optional mode/scale. The scale should be formatted in analogy to the scale in the Transposer parent class. The key should be an instance of ly.pitch.Pitch. """ def __init__(self, key, scale): """ Create scale of pitches from given scale definition. """ import math self.octave = 0 self.modpitches = [0] * 7 for s, a in scale: p = key.copy() self.steps = s self.alter = a super(ModeShifter, self).transpose(p) if self.modpitches[p.note]: self.modpitches[p.note].append(p) else: self.modpitches[p.note] = [p] def closestPitch(self, pitch): """ Get closest pitch from scale. If only one scale note with the same base step exist that is returned. Otherwise the closest is calculated. """ def getNextPitch(step, up=True): modepitch = self.modpitches[step] if modepitch: return modepitch else: if up: step = (step + 1) % 7 else: step = (step - 1) % 7 return getNextPitch(step, up) def comparePitch(pitch, uppitch, dwnpitch): upnum = self.scale[uppitch.note] + uppitch.alter dwnnum = self.scale[dwnpitch.note] + dwnpitch.alter pnum = self.scale[pitch.note] + pitch.alter if upnum - pnum < pnum - dwnnum: return uppitch else: return dwnpitch step = pitch.note modepitch = self.modpitches[step] if modepitch and len(modepitch) == 2: return comparePitch(pitch, modepitch[0], modepitch[1]) else: uppitch = getNextPitch(step)[0] dwnpitch = getNextPitch(step, False)[-1] return comparePitch(pitch, uppitch, dwnpitch) def transpose(self, pitch): """ Shift to closest scale pitch if not already in scale. """ modpitch = self.modpitches[pitch.note] if modpitch: for mp in modpitch: if pitch.note == mp.note and pitch.alter == mp.alter: return clp = self.closestPitch(pitch) self.steps = clp.note - pitch.note if self.steps > 3: self.octave = -1 elif self.steps < -3: self.octave = 1 else: self.octave = 0 self.alter = (self.scale[clp.note] + clp.alter - self.scale[pitch.note] - pitch.alter) super(ModeShifter, self).transpose(pitch) class ModalTransposer(object): """Transpose pitches by number of steps within a given scale. Instantiate with the number of steps (+/-) in the scale to transpose by, and a mode index. The mode index is the index of the major scale in the circle of fifths (C Major = 0). """ def __init__(self, numSteps = 1, scaleIndex = 0): self.numSteps = numSteps self.notes = [0, 1, 2, 3, 4, 5, 6] self.alter = [-0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5] # Initialize to Db, then update to desired mode for i in range(0, scaleIndex): keyNameIndex = ((i+1)*4)%len(self.notes) accidentalIndex = (keyNameIndex-1)%len(self.notes) self.alter[accidentalIndex] += .5 @staticmethod def getKeyIndex(text): """Get the index of the key in the circle of fifths. 'Cb' returns 0, 'C' returns 7, 'B#' returns 14. """ circleOfFifths = ['Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb', 'F', 'C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#'] return circleOfFifths.index(text.capitalize()) def transpose(self, pitch): # Look for an exact match: otherwise, # look for the letter name and save the accidental for i in range(len(self.notes)): if pitch.note == self.notes[i] and pitch.alter == self.alter: fromScaleDeg = i accidental = 0 break else: fromScaleDeg = self.notes.index(pitch.note) accidental = pitch.alter - self.alter[fromScaleDeg] toOctaveMod, toScaleDeg = divmod(fromScaleDeg + self.numSteps, 7) pitch.note = self.notes[toScaleDeg] pitch.alter = self.alter[toScaleDeg] + accidental pitch.octave += toOctaveMod def transpose(cursor, transposer, language="nederlands"): """Transpose pitches using the specified transposer.""" start = cursor.start cursor.start = 0 source = ly.document.Source(cursor, True, tokens_with_position=True) pitches = ly.pitch.PitchIterator(source, language) psource = pitches.pitches() class gen(object): def __iter__(self): return self def __next__(self): while True: t = next(psource) if isinstance(t, (ly.lex.Space, ly.lex.Comment)): continue # Handle stuff that's the same in relative and absolute here if t == "\\relative": relative() elif isinstance(t, ly.lex.lilypond.MarkupScore): absolute(context()) elif isinstance(t, ly.lex.lilypond.ChordMode): chordmode() elif isinstance(t, ly.lex.lilypond.Command) and t == '\\stringTuning': string_tuning() elif isinstance(t, ly.lex.lilypond.PitchCommand): if t == "\\transposition": next(psource) # skip pitch elif t == "\\transpose": for p in getpitches(context()): transpose(p) elif t == "\\key": for p in getpitches(context()): transpose(p, 0) else: return t else: return t next = __next__ tsource = gen() def in_selection(p): """Return True if the pitch or token p may be replaced, i.e. was selected.""" return start == 0 or pitches.position(p) >= start def getpitches(iterable): """Consumes iterable but only yields Pitch instances.""" for p in iterable: if isinstance(p, ly.pitch.Pitch): yield p def context(): """Consume tokens till the level drops (we exit a construct).""" depth = source.state.depth() for t in tsource: yield t if source.state.depth() < depth: return def consume(): """Consume tokens from context() returning the last token, if any.""" t = None for t in context(): pass return t def transpose(p, resetOctave = None): """Transpose absolute pitch, using octave if given.""" transposer.transpose(p) if resetOctave is not None: p.octave = resetOctave if in_selection(p): pitches.write(p) def chordmode(): r"""Called inside \chordmode or \chords.""" for p in getpitches(context()): transpose(p, 0) def string_tuning(): r"""Called after \stringTuning. Ignores the following chord expression.""" for t in tsource: if isinstance(t, ly.lex.lilypond.ChordStart): consume() break def absolute(tokens): r"""Called when outside a possible \relative environment.""" for p in getpitches(tokens): transpose(p) def relative(): r"""Called when \relative is encountered.""" def transposeRelative(p, lastPitch): """Transposes a relative pitch; returns the pitch in absolute form.""" # absolute pitch determined from untransposed pitch of lastPitch p.makeAbsolute(lastPitch) if not in_selection(p): return p # we may change this pitch. Make it relative against the # transposed lastPitch. try: last = lastPitch.transposed except AttributeError: last = lastPitch # transpose a copy and store that in the transposed # attribute of lastPitch. Next time that is used for # making the next pitch relative correctly. newLastPitch = p.copy() transposer.transpose(p) newLastPitch.transposed = p.copy() if p.octavecheck is not None: p.octavecheck = p.octave p.makeRelative(last) if relPitch: # we are allowed to change the pitch after the # \relative command. lastPitch contains this pitch. lastPitch.octave += p.octave p.octave = 0 pitches.write(lastPitch) del relPitch[:] pitches.write(p) return newLastPitch lastPitch = None relPitch = [] # we use a list so it can be changed from inside functions # find the pitch after the \relative command t = next(tsource) if isinstance(t, ly.pitch.Pitch): lastPitch = t if in_selection(t): relPitch.append(lastPitch) t = next(tsource) else: lastPitch = ly.pitch.Pitch.c1() while True: # eat stuff like \new Staff == "bla" \new Voice \notes etc. if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator): t = consume() elif isinstance(t, ly.lex.lilypond.NoteMode): t = next(tsource) else: break # now transpose the relative expression if t in ('{', '<<'): # Handle full music expression { ... } or << ... >> for t in context(): if t == '\\octaveCheck': for p in getpitches(context()): lastPitch = p.copy() del relPitch[:] if in_selection(p): transposer.transpose(p) lastPitch.transposed = p pitches.write(p) elif isinstance(t, ly.lex.lilypond.ChordStart): chord = [lastPitch] for p in getpitches(context()): chord.append(transposeRelative(p, chord[-1])) lastPitch = chord[:2][-1] # same or first elif isinstance(t, ly.pitch.Pitch): lastPitch = transposeRelative(t, lastPitch) elif isinstance(t, ly.lex.lilypond.ChordStart): # Handle just one chord for p in getpitches(context()): lastPitch = transposeRelative(p, lastPitch) elif isinstance(t, ly.pitch.Pitch): # Handle just one pitch transposeRelative(t, lastPitch) # Do it! with cursor.document as document: absolute(tsource) python-ly-0.9.3/ly/pitch/transform.py0000644000175000017500000000464112601156071020754 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2011 - 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. """ Transforming music by pitch manipulation. """ from __future__ import unicode_literals import ly.lex.lilypond def retrograde(cursor, language="nederlands"): """Reverses pitches.""" source = ly.document.Source(cursor, True, tokens_with_position=True) pitches = ly.pitch.PitchIterator(source, language) psource = pitches.pitches() plist = [p for p in psource if isinstance(p, ly.pitch.Pitch)] rlist = [r.copy() for r in reversed(plist)] with cursor.document as d: for p, r in zip(plist, rlist): p.note = r.note p.alter = r.alter p.octave = r.octave pitches.write(p) def inversion(cursor, language="nederlands"): """Inversion of the intervals between pitches.""" import ly.pitch.transpose source = ly.document.Source(cursor, True, tokens_with_position=True) pitches = ly.pitch.PitchIterator(source, language) psource = pitches.pitches() prev_note = None with cursor.document as d: for p in psource: if isinstance(p, ly.pitch.Pitch): if prev_note is None: prev_note = refp = p continue transposer = ly.pitch.transpose.Transposer(p, prev_note) prev_note = p.copy() p.note = refp.note p.alter = refp.alter p.octave = refp.octave transposer.transpose(p) refp = p pitches.write(p) python-ly-0.9.3/ly/pitch/translate.py0000644000175000017500000000677612503612775020762 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Translating the language of pitch names """ from __future__ import unicode_literals import ly.document import ly.pitch def translate(cursor, language, default_language="nederlands"): r"""Changes the language of the pitch names. May raise ly.pitch.PitchNameNotAvailable if the current pitch language has no quarter tones. Returns True if there also was a \language or \include language command that was changed. If not and the cursor specified only a part of the document, you could warn the user that a language or include command should be added to the document. Or you could call insert_language to add a language command to the top of the document. """ start = cursor.start cursor.start = 0 source = ly.document.Source(cursor, tokens_with_position=True) pitches = ly.pitch.PitchIterator(source, default_language) tokens = pitches.tokens() writer = ly.pitch.pitchWriter(language) if start > 0: # consume tokens before the selection, following the language source.consume(tokens, start) cursor.start = start changed = False # track change of \language or \include language command with cursor.document as d: for t in tokens: if isinstance(t, ly.lex.lilypond.Note): # translate the pitch name p = pitches.read(t) if p: n = writer(*p) if n != t: d[t.pos:t.end] = n elif isinstance(t, ly.pitch.LanguageName): if t != language: # change the language name in a command d[t.pos:t.end] = language changed = True return changed def insert_language(document, language, version=None): r"""Inserts a language command in the document. The command is inserted at the top or just below the version line. If the LilyPond version specified < (2, 13, 38), the \include command is used, otherwise the newer \language command. """ # maybe TODO: determine version automatically from document if version and version < (2, 13, 38): text = '\\include "{0}.ly"\n' else: text = '\\language "{0}"\n' text = text.format(language) # insert language command on top of file, but below version with document: for b in document: if '\\version' not in document.tokens(b): pos = document.position(b) document[pos:pos] = text break else: pos = document.size() document[pos:pos] = '\n\n' + text python-ly-0.9.3/ly/pitch/rel2abs.py0000644000175000017500000001302012503612674020271 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2011 - 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. """ Convert relative music to absolute music. """ from __future__ import unicode_literals import itertools import ly.lex.lilypond def rel2abs(cursor, language="nederlands"): """Converts pitches from relative to absolute.""" start = cursor.start cursor.start = 0 source = ly.document.Source(cursor, True, tokens_with_position=True) pitches = ly.pitch.PitchIterator(source, language) psource = pitches.pitches() if start > 0: # consume tokens before the selection, following the language t = source.consume(pitches.tokens(), start) if t: psource = itertools.chain((t,), psource) # this class dispatches the tokens. we can't use a generator function # as that doesn't like to be called again while there is already a body # running. class gen(object): def __iter__(self): return self def __next__(self): t = next(psource) while isinstance(t, (ly.lex.Space, ly.lex.Comment)): t = next(psource) if t == '\\relative' and isinstance(t, ly.lex.lilypond.Command): relative(t) t = next(psource) elif isinstance(t, ly.lex.lilypond.MarkupScore): consume() t = next(psource) return t next = __next__ tsource = gen() def makeAbsolute(p, lastPitch): """Makes pitch absolute (honoring and removing possible octaveCheck).""" if p.octavecheck is not None: p.octave = p.octavecheck p.octavecheck = None else: p.makeAbsolute(lastPitch) pitches.write(p) def getpitches(iterable): """Consumes iterable but only yields Pitch instances.""" for p in iterable: if isinstance(p, ly.pitch.Pitch): yield p def context(): """Consume tokens till the level drops (we exit a construct).""" depth = source.state.depth() for t in tsource: yield t if source.state.depth() < depth: return def consume(): """Consume tokens from context() returning the last token, if any.""" t = None for t in context(): pass return t def relative(t): pos = t.pos lastPitch = None t = next(tsource) if isinstance(t, ly.pitch.Pitch): lastPitch = t t = next(tsource) else: lastPitch = ly.pitch.Pitch.c1() # remove the \relative tokens del document[pos:t.pos] while True: # eat stuff like \new Staff == "bla" \new Voice \notes etc. if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator): t = consume() elif isinstance(t, (ly.lex.lilypond.ChordMode, ly.lex.lilypond.NoteMode)): t = next(tsource) else: break # now convert the relative expression to absolute if t in ('{', '<<'): # Handle full music expression { ... } or << ... >> for t in context(): # skip commands with pitches that do not count if isinstance(t, ly.lex.lilypond.PitchCommand): if t == '\\octaveCheck': pos = t.pos for p in getpitches(context()): # remove the \octaveCheck lastPitch = p end = (p.accidental_token or p.octave_token or p.note_token).end del document[pos:end] break else: consume() elif isinstance(t, ly.lex.lilypond.ChordStart): # handle chord chord = [lastPitch] for p in getpitches(context()): makeAbsolute(p, chord[-1]) chord.append(p) lastPitch = chord[:2][-1] # same or first elif isinstance(t, ly.pitch.Pitch): makeAbsolute(t, lastPitch) lastPitch = t elif isinstance(t, ly.lex.lilypond.ChordStart): # Handle just one chord for p in getpitches(context()): makeAbsolute(p, lastPitch) lastPitch = p elif isinstance(t, ly.pitch.Pitch): # Handle just one pitch makeAbsolute(t, lastPitch) # Do it! with cursor.document as document: for t in tsource: pass python-ly-0.9.3/ly/__main__.py0000644000175000017500000000025712461001227017345 0ustar wilbertwilbert00000000000000# included for testing, use: # python -m ly to call the command line interface if __name__ == "__main__": import sys from ly.cli.main import main sys.exit(main()) python-ly-0.9.3/ly/indent.py0000644000175000017500000003045712466051024017120 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ Indent and auto-indent. """ from __future__ import unicode_literals import ly.lex.lilypond import ly.lex.scheme class Indenter(object): # variables indent_tabs = False # use tabs for indent indent_width = 2 # amount of spaces if indent_tabs == False def __init__(self): pass def indent(self, cursor, indent_blank_lines=False): """Indent all lines in the cursor's range. If indent_blank_lines is True, the indent of blank lines is made larger if necessary. If False (the default), the indent of blank lines if not changed if it is shorter than it should be. """ indents = [''] start_block, end_block = cursor.start_block(), cursor.end_block() in_range = False pline = None prev_indent = '' with cursor.document as d: for b in cursor.document: if b == start_block: in_range = True line = Line(d, b) # handle indents of prev line if pline: if pline.indent is not False: prev_indent = pline.indent if pline.indenters: current_indent = indents[-1] for align, indent in pline.indenters: new_indent = current_indent if align: new_indent += ' ' * (align - len(prev_indent)) if indent: new_indent += '\t' if self.indent_tabs else ' ' * self.indent_width indents.append(new_indent) del indents[max(1, len(indents) - line.dedenters_start):] # if we may not change the indent just remember the current if line.indent is not False: if not in_range: indents[-1] = line.indent elif not indent_blank_lines and line.isblank and indents[-1].startswith(line.indent): pass # don't make shorter indents longer on blank lines elif line.indent != indents[-1]: d[d.position(b):d.position(b)+len(line.indent)] = indents[-1] del indents[max(1, len(indents) - line.dedenters_end):] if b == end_block: break pline = line def increase_indent(self, cursor): """Manually add indent to all lines of cursor.""" indent = '\t' if self.indent_tabs else ' ' * self.indent_width with cursor.document as d: for block in cursor.blocks(): ins = d.position(block) tokens = d.tokens(block) if tokens and isinstance(tokens[0], ly.lex.Space): tab_pos = tokens[0].rfind('\t') if tab_pos != -1: ins += tokens[0].pos + tab_pos + 1 else: ins += tokens[0].end d[ins:ins] = indent def decrease_indent(self, cursor): """Manually remove one level of indent from all lines of cursor.""" with cursor.document as d: for block in cursor.blocks(): tokens = d.tokens(block) if tokens: token = tokens[0] if isinstance(token, ly.lex.Space): space = token else: space = token[0:-len(token.lstrip())] pos = d.position(block) end = pos + len(space) if '\t' in space and space.endswith(' '): # strip alignment del d[pos + space.rfind('\t') + 1 : end] elif space.endswith('\t'): # just strip one tab del d[end - 1] elif space.endswith(' ' * self.indent_width): del d[end - self.indent_width: end] else: del d[pos:end] def get_indent(self, document, block): """Return the indent the block currently has. Returns False if the block is not indentable, e.g. when it is part of a multiline string. """ return Line(document, block).indent def compute_indent(self, document, block): """Return the indent the specified block should have. Returns False if the block is not indentable, e.g. when it is part of a multiline string. This method is used to determine the indent of one line, and just looks to previous lines, copying the indent of the line where the current indent depth starts, and/or adding a level of indent or alignment space. Use this method only for one line or the first of a group you're indenting. """ line = Line(document, block) if line.indent is False: return False depth = line.dedenters_start blocks = document.blocks_backward(document.previous_block(block)) align, indent = None, False for b in blocks: line = Line(document, b) indents = len(line.indenters) if 0 <= depth < indents: # we found the indent token index = indents - depth - 1 align, indent = line.indenters[index] break depth -= indents depth += line.dedenters_end if depth == 0: # same indent as this line break depth += line.dedenters_start else: return "" # here we arrive after 'break' i = line.indent if i is False: for b in blocks: i = Line(document, b).indent if i is not False: break else: i = "" if align: i += ' ' * (align - len(i)) if indent: i += '\t' if self.indent_tabs else ' ' * self.indent_width return i class Line(object): """Brings together all relevant information about a line (block).""" def __init__(self, document, block): """Initialize with a block (line) of the document. After init, the following attributes are set: indent The indent the current line has. This is a string containing whitespace (i.e. spaces and/or tabs) which can be empty. A special case is False, which means the current line is not indentable, e.g. it is a multiline string and should never be automatically be re-indented. isblank True if the line is empty or white-space only. It is False when the indent attribute is also False (e.g. when the line is part of a multiline string). dedenters_start The number of dedent tokens that should cause the indenter to go a level up. dedenters_end The number of dedent tokens that should cause the next line to go a level up. indenters A list of tuples (align, indent). Each item corresponds with an indent that starts on the line. The align value (integer) determines the position the next line should be padded to with spaces, 0 or None means no alignment. The indent value (bool) specifies if there should a new indent level be added (a tab or some amount of spaces). """ state = document.state(block) tokens = document.tokens(block) # are we in a multi-line string? if isinstance(state.parser(), ( ly.lex.lilypond.ParseString, ly.lex.scheme.ParseString, )): self.indent = False self.isblank = False # or a multi-line comment? elif isinstance(state.parser(), ( ly.lex.lilypond.ParseBlockComment, ly.lex.scheme.ParseBlockComment, )): # do allow indent the last line of a block comment if it only # contains space if tokens and isinstance(tokens[0], ly.lex.BlockCommentEnd): self.indent = "" elif (len(tokens) > 1 and isinstance(tokens[0], ly.lex.BlockComment) and isinstance(tokens[1], ly.lex.BlockCommentEnd) and tokens[0].isspace()): self.indent = tokens[0] else: self.indent = False self.isblank = False elif tokens and isinstance(tokens[0], ly.lex.Space): self.indent = tokens[0] self.isblank = len(tokens) == 1 else: self.indent = "" self.isblank = not tokens find_dedenters = True self.dedenters_start = 0 self.dedenters_end = 0 # quickly iter over the tokens, collecting the indent tokens and # possible stuff to align to after the indent tokens indenters = [] for t in tokens: if isinstance(t, ly.lex.Indent): find_dedenters = False if indenters: indenters[-1].append(t) indenters.append([t]) elif isinstance(t, ly.lex.Dedent): if find_dedenters and not isinstance(t, ly.lex.scheme.CloseParen): self.dedenters_start += 1 else: find_dedenters = False if indenters: indenters.pop() else: self.dedenters_end += 1 elif not isinstance(t, ly.lex.Space): find_dedenters = False if indenters: indenters[-1].append(t) # now analyse the indent tokens that are not closed on the same line # and determine how the next line should be indented self.indenters = [] for indent in indenters: token, rest = indent[0], indent[1:] if isinstance(token, ly.lex.scheme.OpenParen): if len(rest) > 1 and self.is_alignable_scheme_keyword(rest[0]): align, indent = rest[1].pos, False elif len(rest) == 1 and not isinstance(rest[0], ly.lex.Comment): align, indent = rest[0].pos, False else: align, indent = token.pos, True elif rest and not isinstance(rest[0], ly.lex.Comment): align, indent = rest[0].pos, False else: align, indent = None, True self.indenters.append((align, indent)) def is_alignable_scheme_keyword(self, token): """Return True if token is an alignable Scheme word like "if", etc.""" return isinstance(token, ly.lex.scheme.Word) and token in ( # Scheme commands that can have one argument on the same line and # then want the next arguments on the next lines at the same # position. 'if', 'and', 'or', 'set!', '=', '<', '<=', '>', '>=', 'eq?', 'eqv?', 'equal?', 'filter', ) python-ly-0.9.3/ly/node.py0000644000175000017500000002520412461641363016564 0ustar wilbertwilbert00000000000000# node.py -- Node is a list-like type to build tree structures with # # Copyright (c) 2008 - 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 Node class. (c) 2008-2011 Wilbert Berendsen License: GPL. This module contains the Node class that can be used as a simple DOM (Document Object Model) for building a tree structure. A Node has children with list-like access methods and keeps also a reference to its parent. A Node can have one parent; appending a Node to another Node causes it to be removed from its parent node (if any). To iterate over the children of a Node:: for n in node: do_something(n) To get the list of children of a Node:: children = list(node) Of course you can get the children directly using:: child = node[3] You should inherit from Node to make meaningful tree node types, e.g. to add custom attributes or multiple sub-types. A WeakNode class is provided as well, which uses a weak reference to the parent, so that no cyclic references are created which might improve garbage collection. """ import weakref class Node(object): """A list-like class to build tree structures with.""" __slots__ = ('__weakref__', '_parent', '_children') def __init__(self, parent=None): self._parent = None self._children = [] if parent: parent.append(self) def _own(self, node): """(Internal) Remove node from its parent if any and make us parent.""" parent = node.parent() if parent: parent.remove(node) node._set_parent(self) def _set_parent(self, node): """(Internal) Set the Node (or None) as our parent.""" self._parent = node def parent(self): """The parent, or None if the node has no parent.""" return self._parent def index(self, node): """Return the index of the given child node.""" return self._children.index(node) def append(self, node): """Append a node to the current node. It will be reparented, that means it will be removed from it's former parent if it had one. """ self._own(node) self._children.append(node) def extend(self, iterable): """Append every Node from iterable.""" for node in iterable: self.append(node) def insert(self, index, node): """Insert a node at the specified index.""" self._own(node) self._children.insert(index, node) def insert_before(self, other, node): """Insert a node before the other node.""" i = self.index(other) self._own(node) self._children.insert(i, node) def remove(self, node): """Remove the given child node.""" self._children.remove(node) node._set_parent(None) def __bool__(self): """We are always true.""" return True __nonzero__ = __bool__ # py2 compat def __len__(self): """Return the number of children.""" return len(self._children) def __getitem__(self, k): """Return child at index or children at slice.""" return self._children[k] def __setitem__(self, k, obj): """Set child at index or children at slice.""" old = self._children[k] if isinstance(k, slice): if k.step: # extended slice, number of items must be same self._children[k] = obj # if this succeeded it's OK new = self._children[k] else: new = tuple(obj) self._children[k] = new for node in old: node._set_parent(None) for node in new: self._own(node) else: old._set_parent(None) self._children[k] = obj self._own(obj) def __delitem__(self, k): """Delete child at index or children at slice.""" if isinstance(k, slice): for node in self._children[k]: node._set_parent(None) else: self._children[k]._set_parent(None) del self._children[k] def __contains__(self, node): """Return True if the node is our child.""" return node in self._children def clear(self): """Remove all children.""" del self[:] def unlink(self): """Remove all children and unlink() them as well.""" for node in self: node.unlink() del self._children[:] def replace(self, old, new): """Replace a child node with another node.""" i = self.index(old) self[i] = new def sort(self, key=None, reverse=False): """Sorts the children, optionally using the key function. Using a key function is recommended, or you must add comparison methods to your Node subclass. """ self._children.sort(key, reverse=reverse) def copy(self): """Return a deep copy of the node and its children """ obj = self.__class__.__new__(self.__class__) obj._parent = None obj._children = [] self._copy_attrs(obj) for n in self: obj.append(n.copy()) return obj def _copy_attrs(self, node): """Called by copy(); copy attributes not starting with '_'.""" for name, value in vars(self).items(): name.startswith("_") or setattr(node, name, value) def ancestors(self): """Climb the tree up over the parents.""" node = self.parent() while node: yield node node = node.parent() def previous_sibling(self): """Return the sibling object just before us in our parents list. Returns None if this is the first child, or if we have no parent. """ for i in self.backward(): return i def next_sibling(self): """Return the sibling object just after us in our parents list. Returns None if this is the last child, or if we have no parent. """ for i in self.forward(): return i def backward(self): """Iterate (backwards) over the preceding siblings.""" parent = self.parent() if parent: i = parent.index(self) return iter(parent[i-1::-1]) def forward(self): """Iterate over the following siblings.""" parent = self.parent() if parent: i = parent.index(self) return iter(parent[i+1::]) def is_descendant_of(self, parent): """Return True if self is a descendant of parent, else False.""" for n in self.ancestors(): if n is parent: return True return False def toplevel(self): """Return the toplevel parent Node of this node.""" node = self parent = self.parent() while parent: node = parent parent = node.parent() return node def descendants(self, depth = -1): """Yield all the descendants, in tree order. Same as iter_depth().""" return self.iter_depth(depth) def iter_depth(self, depth = -1): """Iterate over all the children, and their children, etc. Set depth to restrict the search to a certain depth, -1 is unrestricted. """ if depth != 0: for i in self: yield i for j in i.iter_depth(depth - 1): yield j def iter_rings(self, depth = -1): """Iterate over the children in rings, depth last. This method returns the closest descendants first. Set depth to restrict the search to a certain depth, -1 is unrestricted. """ children = list(self) while children and depth: depth -= 1 newchildren = [] for i in children: yield i newchildren.extend(i) children = newchildren def find(self, cls, depth = -1): """Yield all descendants if they are an instance of cls. cls may also be a tuple of classes. This method uses iter_depth(). """ for node in self.iter_depth(depth): if isinstance(node, cls): yield node def find_children(self, cls, depth = -1): """Yield all descendants if they are an instance of cls. cls may also be a tuple of classes. This method uses iter_rings(). """ for node in self.iter_rings(depth): if isinstance(node, cls): yield node def find_child(self, cls, depth = -1): """Return the first descendant that's an instance of cls. cls may also be a tuple of classes. This method uses iter_rings(). """ for node in self.iter_rings(depth): if isinstance(node, cls): return node def find_parent(self, cls): """Find an ancestor that's an instance of the given class. cls may also be a tuple of classes. """ for node in self.ancestors(): if isinstance(node, cls): return node def dump(self): """Return a string representation of the tree.""" def line(obj, indent): yield indent * " " + repr(obj) for c in obj: for l in line(c, indent + 1): yield l return '\n'.join(line(self, 0)) class WeakNode(Node): """A Node type using a weak reference to the parent.""" __slots__ = () def _set_parent(self, node): self._parent = None if node is None else weakref.ref(node) def parent(self): """The parent, or None if the node has no parent.""" if self._parent is not None: return self._parent() python-ly-0.9.3/ly/words.py0000644000175000017500000005653312533517330017002 0ustar wilbertwilbert00000000000000# This file is part of python-ly, https://pypi.python.org/pypi/python-ly # # Copyright (c) 2008 - 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. """ LilyPond reserved words for auto completion and highlighting. """ from __future__ import unicode_literals lilypond_keywords = ( 'accepts', 'alias', 'consists', 'defaultchild', 'denies', #'description', #'grobdescriptions', 'hide', # since 2.18 'include', #'invalid', 'language', 'name', #'objectid', 'omit', # since 2.18 'once', 'set', 'unset', 'override', 'revert', 'remove', 'temporary', # since 2.18 #'sequential', #'simultaneous', #'type', 'undo', # since 2.18 (not mentioned in the command index) 'version', 'score', 'book', 'bookpart', 'header', 'paper', 'midi', 'layout', 'with', 'context', ) lilypond_music_commands = ( 'absolute', # since 2.18 'acciaccatura', 'accidentalStyle', # since 2.16 'addChordShape', # since 2.16 'addInstrumentDefinition', 'addlyrics', 'addQuote', 'afterGrace', #'afterGraceFraction', # this is a parser variable 'aikenHeads', 'aikenHeadsMinor', 'allowPageTurn', 'alterBroken', # since 2.18 (?) 'alternative', #'AncientRemoveEmptyStaffContext', 'appendToTag', # since 2.16 'applyContext', 'applyMusic', 'applyOutput', 'appoggiatura', 'arpeggio', 'arpeggioArrowDown', 'arpeggioArrowUp', 'arpeggioBracket', 'arpeggioNormal', 'arpeggioParenthesis', 'ascendens', 'auctum', 'augmentum', 'autoAccidentals', 'autoBeamOff', 'autoBeamOn', 'autochange', 'balloonGrobText', 'balloonLengthOff', 'balloonLengthOn', 'balloonText', 'bar', 'barNumberCheck', 'bassFigureExtendersOff', 'bassFigureExtendersOn', 'bassFigureStaffAlignmentDown', 'bassFigureStaffAlignmentNeutral', 'bassFigureStaffAlignmentUp', 'bendAfter', 'blackTriangleMarkup', 'bookOutputName', 'bookOutputSuffix', 'bracketCloseSymbol', 'bracketOpenSymbol', 'break', 'breathe', 'breve', 'cadenzaOff', 'cadenzaOn', 'caesura', 'cavum', 'change', 'chordmode', #'chordNameSeparator', #'chordPrefixSpacer', #'chordRootNamer', 'chordRepeats', # since 2.16 'chords', 'clef', 'cm', 'compoundMeter', # since 2.16 'compressFullBarRests', 'context', 'cr', 'cresc', 'crescHairpin', 'crescTextCresc', 'crossStaff', # since 2.16 'cueClef', # since 2.16 'cueClefUnset', # since 2.16 'cueDuring', 'cueDuringWithClef', # since 2.16 'dashBar', 'dashDash', 'dashDot', 'dashHat', 'dashLarger', 'dashPlus', 'dashUnderscore', 'deadNote', # since 2.16 'decr', 'default', 'defaultNoteHeads', # since 2.16 'defaultTimeSignature', 'defineBarLine', # since 2.18 'deminutum', 'denies', 'descendens', 'dim', 'dimHairpin', 'dimTextDecr', 'dimTextDecresc', 'dimTextDim', 'displayLilyMusic', 'displayMusic', 'divisioMaior', 'divisioMaxima', 'divisioMinima', 'dotsDown', 'dotsNeutral', 'dotsUp', 'drummode', 'drumPitchTable', 'drums', 'dynamicDown', 'dynamicNeutral', 'dynamicUp', 'easyHeadsOff', 'easyHeadsOn', 'endcr', 'endcresc', 'enddecr', 'enddim', 'endincipit', 'endSpanners', 'episemFinis', 'episemInitium', 'escapedBiggerSymbol', 'escapedExclamationSymbol', 'escapedParenthesisCloseSymbol', 'escapedParenthesisOpenSymbol', 'escapedSmallerSymbol', 'expandFullBarRests', 'f', 'featherDurations', 'fermataMarkup', 'ff', 'fff', 'ffff', 'fffff', 'figuremode', 'figures', 'finalis', 'fingeringOrientations', 'flexa', 'fp', 'frenchChords', 'fullJazzExceptions', 'funkHeads', 'funkHeadsMinor', 'fz', 'germanChords', 'glissando', 'grace', 'graceSettings', 'harmonic', 'hideNotes', 'hideStaffSwitch', 'huge', 'ignatzekExceptionMusic', 'ignatzekExceptions', 'iij', 'IIJ', 'ij', 'IJ', 'improvisationOff', 'improvisationOn', 'in', 'inclinatum', 'includePageLayoutFile', 'indent', 'instrumentSwitch', 'instrumentTransposition', 'interscoreline', 'italianChords', 'keepWithTag', 'key', 'killCues', 'label', 'laissezVibrer', 'large', 'ligature', 'linea', 'longa', 'lyricmode', 'lyrics', 'lyricsto', 'maininput', 'majorSevenSymbol', 'makeClusters', 'mark', 'markLengthOff', # since 2.18 'markLengthOn', # since 2.18 'markup', 'markuplines', # deprecated, till 2.14 'markuplist', # from 2.16 'maxima', 'melisma', 'melismaEnd', 'mergeDifferentlyDottedOff', 'mergeDifferentlyDottedOn', 'mergeDifferentlyHeadedOff', 'mergeDifferentlyHeadedOn', 'mf', 'mm', 'mp', 'musicMap', 'neumeDemoLayout', 'new', 'newSpacingSection', 'noBeam', 'noBreak', 'noPageBreak', 'noPageTurn', 'normalsize', 'notemode', 'numericTimeSignature', 'octaveCheck', 'oldaddlyrics', 'oneVoice', 'oriscus', 'ottava', 'override', 'overrideProperty', 'overrideTimeSignatureSettings', # since 2.16 'p', 'pageBreak', 'pageTurn', 'palmMute', # since 2.16 'palmMuteOn', # since 2.16 'parallelMusic', 'parenthesisCloseSymbol', 'parenthesisOpenSymbol', 'parenthesize', 'partcombine', 'partCombineListener', 'partial', 'partialJazzExceptions', 'partialJazzMusic', 'pes', 'phrasingSlurDashed', 'phrasingSlurDotted', 'phrasingSlurDown', 'phrasingSlurNeutral', 'phrasingSlurSolid', 'phrasingSlurUp', 'pipeSymbol', 'pitchedTrill', 'pointAndClickOff', 'pointAndClickOn', 'pp', 'ppp', 'pppp', 'ppppp', 'predefinedFretboardsOff', 'predefinedFretboardsOn', 'pt', 'pushToTag', # since 2.16 'quilisma', 'quoteDuring', 'relative', 'RemoveEmptyRhythmicStaffContext', 'RemoveEmptyStaffContext', 'removeWithTag', 'repeat', 'repeatTie', 'resetRelativeOctave', 'responsum', 'rest', 'revert', 'rfz', 'rightHandFinger', 'sacredHarpHeads', 'sacredHarpHeadsMinor', 'scaleDurations', 'scoreTweak', 'semiGermanChords', 'set', 'sf', 'sff', 'sfp', 'sfz', 'shape', # since 2.16 'shiftDurations', 'shiftOff', 'shiftOn', 'shiftOnn', 'shiftOnnn', 'showStaffSwitch', 'single', # since 2.18 'skip', 'skipTypesetting', 'slurDashed', 'slurDotted', 'slurDown', 'slurNeutral', 'slurSolid', 'slurUp', 'small', 'sostenutoOff', 'sostenutoOn', 'southernHarmonyHeads', 'southernHarmonyHeadsMinor', 'sp', 'spacingTweaks', 'spp', 'startAcciaccaturaMusic', 'startAppoggiaturaMusic', 'startGraceMusic', 'startGroup', 'startStaff', 'startTextSpan', 'startTrillSpan', 'stemDown', 'stemNeutral', 'stemUp', 'stopAcciaccaturaMusic', 'stopAppoggiaturaMusic', 'stopGraceMusic', 'stopGroup', 'stopStaff', 'stopTextSpan', 'stopTrillSpan', 'stringTuning', # since 2.16 'strokeFingerOrientations', 'stropha', 'sustainOff', 'sustainOn', 'tabFullNotation', 'tag', 'teeny', 'tempo', 'tempoWholesPerMinute', 'textLengthOff', 'textLengthOn', 'textSpannerDown', 'textSpannerNeutral', 'textSpannerUp', 'tieDashed', 'tieDotted', 'tieDown', 'tieNeutral', 'tieSolid', 'tieUp', 'tildeSymbol', 'time', 'times', 'timing', 'tiny', 'tocItem', # since ? 'transpose', 'transposedCueDuring', 'transposition', 'treCorde', 'tuplet', # since 2.18 'tupletDown', 'tupletNeutral', 'tupletUp', 'tweak', 'unaCorda', 'unfoldRepeats', 'unHideNotes', 'unit', 'unset', 'versus', 'virga', 'virgula', 'voiceFour', 'voiceFourStyle', 'voiceNeutralStyle', 'voiceOne', 'voiceOneStyle', 'voiceThree', 'voiceThreeStyle', 'voiceTwo', 'voiceTwoStyle', 'walkerHeads', 'walkerHeadsMinor', 'whiteTriangleMarkup', 'withMusicProperty', ) articulations = ( 'accent', 'espressivo', 'marcato', 'portato', 'staccatissimo', 'staccato', 'tenuto', ) ornaments = ( 'prall', 'mordent', 'prallmordent', 'turn', 'upprall', 'downprall', 'upmordent', 'downmordent', 'lineprall', 'prallprall', 'pralldown', 'prallup', 'reverseturn', 'trill', ) fermatas = ( 'shortfermata', 'fermata', 'longfermata', 'verylongfermata', ) instrument_scripts = ( 'upbow', 'downbow', 'flageolet', 'thumb', 'snappizzicato', 'open', 'halfopen', 'stopped', 'lheel', 'rheel', 'ltoe', 'rtoe', ) repeat_scripts = ( 'segno', 'coda', 'varcoda', ) ancient_scripts = ( 'ictus', 'accentus', 'circulus', 'semicirculus', 'signumcongruentiae', ) modes = ( 'major', 'minor', 'ionian', 'dorian', 'phrygian', 'lydian', 'mixolydian', 'aeolian', 'locrian', ) markupcommands_nargs = ( # no arguments ( 'doubleflat', 'doublesharp', 'eyeglasses', 'flat', 'natural', 'null', 'semiflat', 'semisharp', 'sesquiflat', 'sesquisharp', 'sharp', 'strut', 'table-of-contents' ), # one argument ( 'backslashed-digit', 'bold', 'box', 'bracket', 'caps', 'center-align', 'center-column', 'char', 'circle', 'column', 'concat', 'dir-column', 'draw-dashed-line', # since 2.18 'draw-dotted-line', # since 2.18 'draw-line', 'dynamic', 'fill-line', 'finger', 'fontCaps', 'fret-diagram', 'fret-diagram-terse', 'fret-diagram-verbose', 'fromproperty', 'harp-pedal', 'hbracket', 'hspace', 'huge', 'italic', 'justify', 'justify-field', 'justify-string', 'large', 'larger', 'left-align', 'left-brace', 'left-column', 'line', 'lookup', 'markalphabet', 'markletter', 'medium', 'musicglyph', 'normalsize', 'normal-size-sub', 'normal-size-super', 'normal-text', 'number', 'oval', # since 2.18 'postscript', 'right-align', 'right-brace', 'right-column', 'roman', 'rounded-box', 'sans', 'score', 'simple', 'slashed-digit', 'small', 'smallCaps', 'smaller', 'stencil', 'sub', 'super', 'teeny', 'text', 'tied-lyric', 'tiny', 'transparent', 'triangle', 'typewriter', 'underline', 'upright', 'vcenter', 'vspace', 'verbatim-file', 'whiteout', 'wordwrap', 'wordwrap-field', 'wordwrap-string', ), # two arguments ( 'abs-fontsize', 'auto-footnote', # since 2.16 'combine', 'customTabClef', 'fontsize', 'footnote', 'fraction', 'halign', 'hcenter-in', 'lower', 'magnify', 'note', 'on-the-fly', 'override', 'pad-around', 'pad-markup', 'pad-x', 'page-link', 'path', # added in LP 2.13.31 'raise', 'rotate', 'scale', 'translate', 'translate-scaled', 'with-color', 'with-link', 'with-url', 'woodwind-diagram', ), # three arguments ( 'arrow-head', 'beam', 'draw-circle', 'eps-file', 'filled-box', 'general-align', 'note-by-number', 'pad-to-box', 'page-ref', 'with-dimensions', ), # four arguments ( 'pattern', 'put-adjacent', ), # five arguments, ( 'fill-with-pattern', ), ) markupcommands = sum(markupcommands_nargs, ()) markuplistcommands = ( 'column-lines', 'justified-lines', 'override-lines', 'wordwrap-internal', 'wordwrap-lines', 'wordwrap-string-internal', ) contexts = ( 'ChoirStaff', 'ChordNames', 'CueVoice', 'Devnull', 'DrumStaff', 'DrumVoice', 'Dynamics', 'FiguredBass', 'FretBoards', 'Global', 'GrandStaff', 'GregorianTranscriptionStaff', 'GregorianTranscriptionVoice', 'KievanStaff', # since 2.16 'KievanVoice', # since 2.16 'Lyrics', 'MensuralStaff', 'MensuralVoice', 'NoteNames', 'NullVoice', # since 2.18 'PetrucciStaff', # since 2.16 'PetrucciVoice', # since 2.16 'PianoStaff', 'RhythmicStaff', 'Score', 'Staff', 'StaffGroup', 'TabStaff', 'TabVoice', 'Timing', 'VaticanaStaff', 'VaticanaVoice', 'Voice', ) midi_instruments = ( # (1-8 piano) 'acoustic grand', 'bright acoustic', 'electric grand', 'honky-tonk', 'electric piano 1', 'electric piano 2', 'harpsichord', 'clav', # (9-16 chrom percussion) 'celesta', 'glockenspiel', 'music box', 'vibraphone', 'marimba', 'xylophone', 'tubular bells', 'dulcimer', # (17-24 organ) 'drawbar organ', 'percussive organ', 'rock organ', 'church organ', 'reed organ', 'accordion', 'harmonica', 'concertina', # (25-32 guitar) 'acoustic guitar (nylon)', 'acoustic guitar (steel)', 'electric guitar (jazz)', 'electric guitar (clean)', 'electric guitar (muted)', 'overdriven guitar', 'distorted guitar', 'guitar harmonics', # (33-40 bass) 'acoustic bass', 'electric bass (finger)', 'electric bass (pick)', 'fretless bass', 'slap bass 1', 'slap bass 2', 'synth bass 1', 'synth bass 2', # (41-48 strings) 'violin', 'viola', 'cello', 'contrabass', 'tremolo strings', 'pizzicato strings', 'orchestral harp', # till LilyPond 2.12 was this erroneously called: 'orchestral strings' 'timpani', # (49-56 ensemble) 'string ensemble 1', 'string ensemble 2', 'synthstrings 1', 'synthstrings 2', 'choir aahs', 'voice oohs', 'synth voice', 'orchestra hit', # (57-64 brass) 'trumpet', 'trombone', 'tuba', 'muted trumpet', 'french horn', 'brass section', 'synthbrass 1', 'synthbrass 2', # (65-72 reed) 'soprano sax', 'alto sax', 'tenor sax', 'baritone sax', 'oboe', 'english horn', 'bassoon', 'clarinet', # (73-80 pipe) 'piccolo', 'flute', 'recorder', 'pan flute', 'blown bottle', 'shakuhachi', 'whistle', 'ocarina', # (81-88 synth lead) 'lead 1 (square)', 'lead 2 (sawtooth)', 'lead 3 (calliope)', 'lead 4 (chiff)', 'lead 5 (charang)', 'lead 6 (voice)', 'lead 7 (fifths)', 'lead 8 (bass+lead)', # (89-96 synth pad) 'pad 1 (new age)', 'pad 2 (warm)', 'pad 3 (polysynth)', 'pad 4 (choir)', 'pad 5 (bowed)', 'pad 6 (metallic)', 'pad 7 (halo)', 'pad 8 (sweep)', # (97-104 synth effects) 'fx 1 (rain)', 'fx 2 (soundtrack)', 'fx 3 (crystal)', 'fx 4 (atmosphere)', 'fx 5 (brightness)', 'fx 6 (goblins)', 'fx 7 (echoes)', 'fx 8 (sci-fi)', # (105-112 ethnic) 'sitar', 'banjo', 'shamisen', 'koto', 'kalimba', 'bagpipe', 'fiddle', 'shanai', # (113-120 percussive) 'tinkle bell', 'agogo', 'steel drums', 'woodblock', 'taiko drum', 'melodic tom', 'synth drum', 'reverse cymbal', # (121-128 sound effects) 'guitar fret noise', 'breath noise', 'seashore', 'bird tweet', 'telephone ring', 'helicopter', 'applause', 'gunshot', # (channel 10 drum-kits - subtract 32768 to get program no.) 'standard kit', 'standard drums', 'drums', 'room kit', 'room drums', 'power kit', 'power drums', 'rock drums', 'electronic kit', 'electronic drums', 'tr-808 kit', 'tr-808 drums', 'jazz kit', 'jazz drums', 'brush kit', 'brush drums', 'orchestra kit', 'orchestra drums', 'classical drums', 'sfx kit', 'sfx drums', 'mt-32 kit', 'mt-32 drums', 'cm-64 kit', 'cm-64 drums', ) #scheme_functions = ( # 'set-accidental-style', # 'set-global-staff-size', # 'set-octavation', # 'set-paper-size', # 'define-public', # 'define-music-function', # 'define-markup-command', # 'empty-stencil', # 'markup', # 'number?', # 'string?', # 'pair?', # 'ly:duration?', # 'ly:grob?', # 'ly:make-moment', # 'ly:make-pitch', # 'ly:music?', # 'ly:moment?', # 'ly:format', # 'markup?', # 'interpret-markup', # 'make-line-markup', # 'make-center-markup', # 'make-column-markup', # 'make-musicglyph-markup', # 'color?', # 'rgb-color', # 'x11-color', #) scheme_values = ( 'UP', 'DOWN', 'LEFT', 'RIGHT', 'CENTER', 'minimum-distance', 'basic-distance', 'padding', 'stretchability', ) headervariables = ( 'dedication', 'title', 'subtitle', 'subsubtitle', 'poet', 'composer', 'meter', 'opus', 'arranger', 'instrument', 'piece', 'breakbefore', 'copyright', 'tagline', 'mutopiatitle', 'mutopiacomposer', 'mutopiapoet', 'mutopiaopus', 'mutopiainstrument', 'date', 'enteredby', 'source', 'style', 'maintainer', 'maintainerEmail', 'maintainerWeb', 'moreInfo', 'lastupdated', 'texidoc', 'footer', ) papervariables = ( # fixed vertical 'paper-height', 'top-margin', 'bottom-margin', 'ragged-bottom', 'ragged-last-bottom', # horizontal 'paper-width', 'line-width', 'left-margin', 'right-margin', 'check-consistency', 'ragged-right', 'ragged-last', 'two-sided', 'inner-margin', 'outer-margin', 'binding-offset', 'horizontal-shift', 'indent', 'short-indent', # flex vertical 'markup-system-spacing', # the distance between a (title or top-level) markup and the system that follows it. 'score-markup-spacing', # the distance between the last system of a score and the (title or top-level) markup that follows it. 'score-system-spacing', # the distance between the last system of a score and the first system of the score that follows it, when no (title or top-level) markup exists between them. 'system-system-spacing', # the distance between two systems in the same score. 'markup-markup-spacing', # the distance between two (title or top-level) markups. 'last-bottom-spacing', # the distance from the last system or top-level markup on a page to the bottom of the printable area (i.e. the top of the bottom margin). 'top-system-spacing', # the distance from the top of the printable area (i.e. the bottom of the top margin) to the first system on a page, when there is no (title or top-level) markup between the two. 'top-markup-spacing', # the distance from the top of the printable area (i.e. the bottom of the top margin) to the first (title or top-level) markup on a page, when there is no system between the two. # line breaking 'max-systems-per-page', 'min-systems-per-page', 'system-count', 'systems-per-page', # page breaking 'blank-after-score-page-force', # The penalty for having a blank page after the end of one score and before the next. By default, this is smaller than blank-page-force, so that we prefer blank pages after scores to blank pages within a score. 'blank-last-page-force', # The penalty for ending the score on an odd-numbered page. 'blank-page-force', # The penalty for having a blank page in the middle of a score. This is not used by ly:optimal-breaking since it will never consider blank pages in the middle of a score. 'page-breaking', # The page-breaking algorithm to use. Choices are ly:minimal-breaking, ly:page-turn-breaking, and ly:optimal-breaking. 'page-breaking-system-system-spacing', # Tricks the page breaker into thinking that system-system-spacing is set to something different than it really is. For example, if page-breaking-system-system-spacing #'padding is set to something substantially larger than system-system-spacing #'padding, then the page-breaker will put fewer systems on each page. Default: unset. 'page-count', # The number of pages to be used for a score, unset by default. # page numbering 'auto-first-page-number', 'first-page-number', 'print-first-page-number', 'print-page-number', # misc 'page-spacing-weight', 'print-all-headers', 'system-separator-markup', # debugging 'annotate-spacing', # different markups 'bookTitleMarkup', 'evenFooterMarkup', 'evenHeaderMarkup', 'oddFooterMarkup', 'oddHeaderMarkup', 'scoreTitleMarkup', 'tocItemMarkup', 'tocTitleMarkup', # fonts 'fonts', # undocumented? #'blank-after-score-page-force', #'force-assignment', #'input-encoding', #'output-scale', ) layoutvariables = ( 'indent', 'short-indent', 'system-count', 'line-width', 'ragged-right', 'ragged-last', ) midivariables = ( ) repeat_types = ( 'unfold', 'percent', 'volta', 'tremolo', ) accidentalstyles = ( 'default', 'voice', 'modern', 'modern-cautionary', 'modern-voice', 'modern-voice-cautionary', 'piano', 'piano-cautionary', 'neo-modern', 'neo-modern-cautionary', 'neo-modern-voice', 'neo-modern-voice-cautionary', 'dodecaphonic', 'teaching', 'no-reset', 'forget', ) clefs_plain = ( 'alto', 'baritone', 'bass', 'C', 'F', 'french', 'G', 'GG', # since 2.19 'mezzosoprano', 'percussion', 'soprano', 'subbass', 'tab', 'tenor', 'tenorG', # since 2.19 'treble', 'varbaritone', 'varC', # since 2.19 'varpercussion', # since 2.19 'violin', ) clefs = clefs_plain + ( 'treble_8', 'bass_8', ) break_visibility = ( 'all-invisible', 'begin-of-line-visible', 'end-of-line-visible', 'all-visible', 'begin-of-line-invisible', 'end-of-line-invisible', 'center-invisible', ) mark_formatters = ( 'format-mark-alphabet', 'format-mark-barnumbers', 'format-mark-letters', 'format-mark-numbers', 'format-mark-box-alphabet', 'format-mark-box-barnumbers', 'format-mark-box-letters', 'format-mark-box-numbers', 'format-mark-circle-alphabet', 'format-mark-circle-barnumbers', 'format-mark-circle-letters', 'format-mark-circle-numbers', ) python-ly-0.9.3/PKG-INFO0000664000175000017500000000744212636745216015752 0ustar wilbertwilbert00000000000000Metadata-Version: 1.1 Name: python-ly Version: 0.9.3 Summary: Tool and library for manipulating LilyPond files Home-page: https://github.com/wbsoft/python-ly Author: Wilbert Berendsen Author-email: info@frescobaldi.org License: GPL Description: ==================== README for python-ly ==================== This package provides a Python library `ly` containing various Python modules to parse, manipulate or create documents in LilyPond format. A command line program `ly` is also provided that can be used to do various manipulations with LilyPond files. The LilyPond format is a plain text input format that is used by the GNU music typesetter LilyPond (www.lilypond.org). The python-ly package is Free Software, licensed under the GPL. This package is written by the Frescobaldi developers and is used extensively by the Frescobaldi project. The main author is Wilbert Berendsen. | Download from: https://pypi.python.org/pypi/python-ly | Development homepage: https://github.com/wbsoft/python-ly The `ly` command line tool -------------------------- With `ly` you can reformat, or re-indent LilyPond files, transpose music, translate pitch names, convert LilyPond to syntax-colored HTML, etc. There is also experimental support for converting LilyPond to MusicXML. Use:: ly -h to get a full list of the features of the `ly` command. Here is an example to re-indent and transpose a LilyPond file:: ly "indent; transpose c d" -o output.ly file.ly To test the `ly` module from the current directory without installing, use:: python -m ly This will behave like running the `ly` command when the package is installed. The `ly` Python module ---------------------- The `ly` module supports both Python2 and Python3. This is a short description of some modules: * ``ly.slexer``: generic tools to build parsers using regular expressions * ``ly.node``: a generic list-like node object to build tree structures with * ``ly.document``: a tokenized text document (LilyPond file) * ``ly.lex``: a parser for LilyPond, Scheme, and other formats, using `slexer` * ``ly.music``: a tree structure of the contents of a document * ``ly.pitch``: functions for translating, transposing etc * ``ly.indent``: indent LilyPond text * ``ly.reformat``: format LilyPond text * ``ly.dom``: (deprecated) tree structure to build LilyPond text from * ``ly.words``: words for highlighting and autocompletion * ``ly.data``: layout objects, properties, interfaces, font glyphs etc extracted from LilyPond Documentation ------------- The documentation is built using Sphinx and located in the doc directory. If you have Sphinx installed, you can build nicely formatted HTML documentation by typing ``make html`` in the doc directory. You can also read the docs online at http://python-ly.readthedocs.org/. Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Multimedia :: Sound/Audio Classifier: Topic :: Text Editors python-ly-0.9.3/setup.py0000644000175000017500000000334512467070646016363 0ustar wilbertwilbert00000000000000import os import sys try: from setuptools import setup except ImportError: from distutils.core import setup ### python-ly ### ### This setup script packages and/or installs: ### - the ly package ### - the ly script from ly import pkginfo def packagelist(directory): """Return a sorted list with package names for all packages under the given directory.""" folder, basename = os.path.split(directory) return list(sorted(root[len(folder)+1:].replace(os.sep, '.') for root, dirs, files in os.walk(directory) if '__init__.py' in files)) scripts = ['bin/ly'] packages = packagelist('./ly') py_modules = [] with open('README.rst', 'rb') as f: long_description = f.read().decode('utf-8') package_data = { 'ly.xml': ['*.ily', '*.ly'], } #data_files = [ # ('share/man/man1', ['ly.1']), #] classifiers = [ 'Development Status :: 4 - Beta', #'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Multimedia :: Sound/Audio', 'Topic :: Text Editors', ] setup( name = pkginfo.name, version = pkginfo.version, description = pkginfo.description, long_description = long_description, maintainer = pkginfo.maintainer, maintainer_email = pkginfo.maintainer_email, url = pkginfo.url, license = pkginfo.license, scripts = scripts, packages = packages, package_data = package_data, py_modules = py_modules, classifiers = classifiers, #data_files = data_files, ) python-ly-0.9.3/doc/0000775000175000017500000000000012636745216015413 5ustar wilbertwilbert00000000000000python-ly-0.9.3/doc/ly.music.rst0000644000175000017500000000107612460712605017701 0ustar wilbertwilbert00000000000000ly.music package ================ Module contents --------------- .. automodule:: ly.music :members: :undoc-members: :show-inheritance: Submodules ---------- ly.music.event module --------------------- .. automodule:: ly.music.event :members: :undoc-members: :show-inheritance: ly.music.items module --------------------- .. automodule:: ly.music.items :members: :undoc-members: :show-inheritance: ly.music.read module -------------------- .. automodule:: ly.music.read :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/command.rst0000644000175000017500000000017412461114463017551 0ustar wilbertwilbert00000000000000The ``ly`` command ================== .. automodule:: ly.cli.doc :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/ly.xml.rst0000644000175000017500000001437612466573237017404 0ustar wilbertwilbert00000000000000ly.xml package =============== Introduction ------------ This package is concerned with representing a LilyPond structure (e.g. music) as an XML tree. The structure of the tree closely follows LilyPond's data structures. The tree can be parsed or analysed, and could be used to convert the music to other formats like Mei of MusicXML. This package tries to define the exact format (dtd or schema) of the tree. There will be three ways to build the tree: - by hand, by creating XML elements (maybe with use of some helper methods). - from a tokenized document - by LilyPond, using the ``xml-export.ily`` script that is included. The latter case is very interesting as all the music parsing and handling is already done by LilyPond. The exported XML nearly contains all information of a score or music object. Below, some more information about the XML. Note that this is all in heavy development. Module contents --------------- .. automodule:: ly.xml :members: :undoc-members: :show-inheritance: The ``xml-export.ily`` file --------------------------- Written by Wilbert Berendsen, jan-feb 2015 This LilyPond module defines a function (``xml-export``) that converts LilyPond datastructures to XML. For convenience, a ``\displayLilyXML`` music function is added that converts a music expression to XML. Usage e.g.:: \include "/path/to/xml-export.ily" \displayLilyXML { c d e f } The XML closely follows the LilyPond music structure. All ``(make-music 'MusicName ...)`` objects translate to a ```` tag. The music in the ``'element`` and ``'elements`` properties is put in the ```` and ```` tags. (LilyPond uses ``'element`` when there is a single music argument, and ``'elements`` for a list of music arguments, but for example ``\repeat`` uses both: ``'element`` for the repeated music and ``'elements`` for the ``\alternatives``.) Thus ````, if there, always has one ```` child. ````, if there, can have more than one ```` child. Besides ``'element`` and ``'elements``, the following properties of music objects are handled specially: - ``'origin`` => ```` element with ``filename``, ``line`` and ``char`` attributes - ``'pitch`` => ```` element with ``octave``, ``notename`` and ``alteration`` attributes - ``'duration`` => ```` element with ``log``, ``dots``, ``numer`` and ``denom`` attributes - ``'articulations`` => ```` element containing ```` elements - ``'tweaks`` => ```` element containing pairs ``(symbol . value)`` All other properties a music object may have, are translated to a ```` element with a ``name`` attribute. The value is the child element and can be any object (string, list, pair, symbol, number etc.). (Note that the LilyPond command ``\displayMusic`` does not display all properties.) Markup objects are also converted to XML, where a toplevel ```` element is used. The individual markup commands are converted to an ```` element, with the name in the ``name`` attribute (e.g. ````). Arguments to markup commands may be other commands, or other objects (markup ``\score`` even has a score argument, which is also supported). Example ------- This LilyPond music:: \relative { c d e } maps to Scheme (using ``\displayMusic``):: (make-music 'RelativeOctaveMusic 'element (make-music 'SequentialMusic 'elements (list (make-music 'NoteEvent 'pitch (ly:make-pitch -1 0 0) 'duration (ly:make-duration 2 0 1)) (make-music 'NoteEvent 'pitch (ly:make-pitch -1 1 0) 'duration (ly:make-duration 2 0 1)) (make-music 'NoteEvent 'pitch (ly:make-pitch -1 2 0) 'duration (ly:make-duration 2 0 1))))) and maps to XML (using ``\displayLilyXML``):: By default, the XML is written to standard output. To automatically export a full LilyPond document to an XML representation, use the ``xml-export-init.ly`` script with the ``--init`` LilyPond option. That script automatically sets up LilyPond to output one XML document with a ```` root element, containing a ```` element for every book in the LilyPond file. (LilyPond always creates at least one book, collecting all the music or markup at the toplevel.) The ``xml-export-init.ly`` script is intended to be used via the ``--init`` option. It automatically converts every ``\book`` in the score to an XML document. In this case the XML is also written to standard output by default, but you can specify another file with ``-dxml-export=``. So, to convert a LilyPond source file to an XML file containing the LilyPond music structure in XML format, use the following command:: lilypond --init /path/to/xml-export-init.ly -dxml-export=song.xml song.ly The XML document has a ```` root element, containing a ```` element for every book in the LilyPond file. python-ly-0.9.3/doc/ly.rst0000644000175000017500000000445212463263003016557 0ustar wilbertwilbert00000000000000The ``ly`` package ================== Module contents --------------- .. automodule:: ly :members: :undoc-members: :show-inheritance: Subpackages ----------- .. toctree:: ly.lex ly.music ly.musicxml ly.pitch ly.xml ly.data ly.cli Submodules ---------- ly.slexer module ---------------- .. automodule:: ly.slexer :members: :undoc-members: :show-inheritance: ly.document module ------------------ .. automodule:: ly.document :members: :undoc-members: :show-inheritance: ly.docinfo module ----------------- .. automodule:: ly.docinfo :members: :undoc-members: :show-inheritance: ly.barcheck module ------------------ .. automodule:: ly.barcheck :members: :undoc-members: :show-inheritance: ly.colorize module ------------------ .. automodule:: ly.colorize :members: :undoc-members: :show-inheritance: ly.cursortools module --------------------- .. automodule:: ly.cursortools :members: :undoc-members: :show-inheritance: ly.dom module ------------- This module is deprecated. When `ly.music` is able to generate LilyPond code from scratch, this module will be removed. .. automodule:: ly.dom :members: :undoc-members: :show-inheritance: ly.duration module ------------------ .. automodule:: ly.duration :members: :undoc-members: :show-inheritance: ly.etreeutil module ------------------- .. automodule:: ly.etreeutil :members: :undoc-members: :show-inheritance: ly.indent module ---------------- .. automodule:: ly.indent :members: :undoc-members: :show-inheritance: ly.node module -------------- .. automodule:: ly.node :members: :undoc-members: :show-inheritance: ly.pkginfo module ----------------- .. automodule:: ly.pkginfo :members: :member-order: bysource ly.reformat module ------------------ .. automodule:: ly.reformat :members: :undoc-members: :show-inheritance: ly.rhythm module ---------------- .. automodule:: ly.rhythm :members: :undoc-members: :show-inheritance: ly.util module -------------- .. automodule:: ly.util :members: :undoc-members: :show-inheritance: ly.words module --------------- .. automodule:: ly.words :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/ly.musicxml.rst0000644000175000017500000000151612463776572020441 0ustar wilbertwilbert00000000000000ly.musicxml package =================== Module contents --------------- .. automodule:: ly.musicxml :members: :undoc-members: :show-inheritance: Submodules ---------- ly.musicxml.create_musicxml module ---------------------------------- .. automodule:: ly.musicxml.create_musicxml :members: :undoc-members: :show-inheritance: ly.musicxml.ly2xml_mediator module ---------------------------------- .. automodule:: ly.musicxml.ly2xml_mediator :members: :undoc-members: :show-inheritance: ly.musicxml.lymus2musxml module ------------------------------- .. automodule:: ly.musicxml.lymus2musxml :members: :undoc-members: :show-inheritance: ly.musicxml.xml_objs module --------------------------- .. automodule:: ly.musicxml.xml_objs :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/index.rst0000644000175000017500000000226112461215746017247 0ustar wilbertwilbert00000000000000.. python-ly documentation master file, created by sphinx-quickstart on Sat Jan 24 11:57:29 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to python-ly's documentation! ===================================== This package provides a Python library `ly` containing various Python modules to parse, manipulate or create documents in LilyPond format. A command line program `ly` is also provided that can be used to do various manipulations with LilyPond files. The LilyPond format is a plain text input format that is used by the GNU music typesetter LilyPond (www.lilypond.org). The python-ly package is Free Software, licensed under the GPL. This package is written by the Frescobaldi developers and is used extensively by the Frescobaldi project. The main author is Wilbert Berendsen. | Download from: https://pypi.python.org/pypi/python-ly | Development homepage: https://github.com/wbsoft/python-ly This documentation describes python-ly |version|. Contents: .. toctree:: :maxdepth: 4 command ly Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-ly-0.9.3/doc/ly.cli.rst0000644000175000017500000000065512460712535017334 0ustar wilbertwilbert00000000000000ly.cli package ============== Module contents --------------- .. automodule:: ly.cli :members: :undoc-members: :show-inheritance: Submodules ---------- ly.cli.command module --------------------- .. automodule:: ly.cli.command :members: :undoc-members: :show-inheritance: ly.cli.main module ------------------ .. automodule:: ly.cli.main :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/ly.pitch.rst0000644000175000017500000000135312460712633017667 0ustar wilbertwilbert00000000000000ly.pitch package ================ Module contents --------------- .. automodule:: ly.pitch :members: :undoc-members: :show-inheritance: Submodules ---------- ly.pitch.abs2rel module ----------------------- .. automodule:: ly.pitch.abs2rel :members: :undoc-members: :show-inheritance: ly.pitch.rel2abs module ----------------------- .. automodule:: ly.pitch.rel2abs :members: :undoc-members: :show-inheritance: ly.pitch.translate module ------------------------- .. automodule:: ly.pitch.translate :members: :undoc-members: :show-inheritance: ly.pitch.transpose module ------------------------- .. automodule:: ly.pitch.transpose :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/conf.py0000644000175000017500000002421612475566235016720 0ustar wilbertwilbert00000000000000# -*- coding: utf-8 -*- # # ly documentation build configuration file, created by # sphinx-quickstart on Sat Jan 24 11:57:29 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) import ly.pkginfo # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = ly.pkginfo.name copyright = u'2015, ' + ly.pkginfo.maintainer # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = ly.pkginfo.version # The full version, including alpha/beta/rc tags. release = ly.pkginfo.version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'lydoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'python-ly.tex', u'python-ly Documentation', u'Wilbert Berendsen', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('command', 'ly', u'Manipulate LilyPond source files', [ly.pkginfo.maintainer], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', ly.pkginfo.name, u'python-ly Documentation', u'Author', ly.pkginfo.name, ly.pkginfo.description, 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = ly.pkginfo.name epub_author = ly.pkginfo.maintainer epub_publisher = ly.pkginfo.maintainer epub_copyright = u'2015, ' + ly.pkginfo.maintainer # The basename for the epub file. It defaults to the project name. #epub_basename = u'ly' # The HTML theme for the epub output. Since the default themes are not optimized # for small screen space, using the same theme for HTML and epub output is # usually not wise. This defaults to 'epub', a theme designed to save visual # space. #epub_theme = 'epub' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # A tuple containing the cover image and cover page html template filenames. #epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. #epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True # Choose between 'default' and 'includehidden'. #epub_tocscope = 'default' # Fix unsupported image types using the PIL. #epub_fix_images = False # Scale large images. #epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. #epub_show_urls = 'inline' # If false, no index is generated. #epub_use_index = True python-ly-0.9.3/doc/ly.data.rst0000644000175000017500000000051212460712571017466 0ustar wilbertwilbert00000000000000ly.data package =============== Module contents --------------- .. automodule:: ly.data :members: :undoc-members: :show-inheritance: Submodules ---------- ly.data.makeschemedata module ----------------------------- .. automodule:: ly.data.makeschemedata :members: :undoc-members: :show-inheritance: python-ly-0.9.3/doc/Makefile0000644000175000017500000001513112460711207017036 0ustar wilbertwilbert00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ly.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ly.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/ly" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ly" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." python-ly-0.9.3/doc/ly.lex.rst0000644000175000017500000000167612460712565017364 0ustar wilbertwilbert00000000000000ly.lex package ============== Module contents --------------- .. automodule:: ly.lex :members: :undoc-members: :show-inheritance: Submodules ---------- ly.lex.docbook module --------------------- .. automodule:: ly.lex.docbook :members: :undoc-members: :show-inheritance: ly.lex.html module ------------------ .. automodule:: ly.lex.html :members: :undoc-members: :show-inheritance: ly.lex.latex module ------------------- .. automodule:: ly.lex.latex :members: :undoc-members: :show-inheritance: ly.lex.lilypond module ---------------------- .. automodule:: ly.lex.lilypond :members: :undoc-members: :show-inheritance: ly.lex.scheme module -------------------- .. automodule:: ly.lex.scheme :members: :undoc-members: :show-inheritance: ly.lex.texinfo module --------------------- .. automodule:: ly.lex.texinfo :members: :undoc-members: :show-inheritance: python-ly-0.9.3/INSTALL.rst0000644000175000017500000000214512461214725016476 0ustar wilbertwilbert00000000000000========== Installing ========== This package uses a distutils setup script. As the module is written in pure Python, no build procedure is needed. You can install python-ly using:: python setup.py install If you want to install into /usr instead of /usr/local:: python setup.py install --prefix=/usr If you have a Debian-based system such as Ubuntu, and you get an error message like "ImportError: No module named ly.cli.main", try:: python setup.py install --install-layout=deb See the distutils documentation for more install options. Building the documentation -------------------------- The documentation resides in the ``doc`` directory and can be built using the Sphinx toolchain (http://sphinx-doc.org/). Typing ``make html`` in the ``doc`` directory generates full HTML documentation in the ``doc/build/html` directory. Typing ``make man`` in the ``doc`` directory generates a manpage for the ``ly`` command. This manpage is created in ``doc/build/man/ly.1`` and can be installed on UNIX systems in a location like ``/usr/share/man/man1/``. The documentation is not installed by default. python-ly-0.9.3/bin/0000775000175000017500000000000012636745216015416 5ustar wilbertwilbert00000000000000python-ly-0.9.3/bin/ly0000644000175000017500000000011712501542536015751 0ustar wilbertwilbert00000000000000#!/usr/bin/env python import sys from ly.cli.main import main sys.exit(main()) python-ly-0.9.3/README.rst0000644000175000017500000000503012461236541016321 0ustar wilbertwilbert00000000000000==================== README for python-ly ==================== This package provides a Python library `ly` containing various Python modules to parse, manipulate or create documents in LilyPond format. A command line program `ly` is also provided that can be used to do various manipulations with LilyPond files. The LilyPond format is a plain text input format that is used by the GNU music typesetter LilyPond (www.lilypond.org). The python-ly package is Free Software, licensed under the GPL. This package is written by the Frescobaldi developers and is used extensively by the Frescobaldi project. The main author is Wilbert Berendsen. | Download from: https://pypi.python.org/pypi/python-ly | Development homepage: https://github.com/wbsoft/python-ly The `ly` command line tool -------------------------- With `ly` you can reformat, or re-indent LilyPond files, transpose music, translate pitch names, convert LilyPond to syntax-colored HTML, etc. There is also experimental support for converting LilyPond to MusicXML. Use:: ly -h to get a full list of the features of the `ly` command. Here is an example to re-indent and transpose a LilyPond file:: ly "indent; transpose c d" -o output.ly file.ly To test the `ly` module from the current directory without installing, use:: python -m ly This will behave like running the `ly` command when the package is installed. The `ly` Python module ---------------------- The `ly` module supports both Python2 and Python3. This is a short description of some modules: * ``ly.slexer``: generic tools to build parsers using regular expressions * ``ly.node``: a generic list-like node object to build tree structures with * ``ly.document``: a tokenized text document (LilyPond file) * ``ly.lex``: a parser for LilyPond, Scheme, and other formats, using `slexer` * ``ly.music``: a tree structure of the contents of a document * ``ly.pitch``: functions for translating, transposing etc * ``ly.indent``: indent LilyPond text * ``ly.reformat``: format LilyPond text * ``ly.dom``: (deprecated) tree structure to build LilyPond text from * ``ly.words``: words for highlighting and autocompletion * ``ly.data``: layout objects, properties, interfaces, font glyphs etc extracted from LilyPond Documentation ------------- The documentation is built using Sphinx and located in the doc directory. If you have Sphinx installed, you can build nicely formatted HTML documentation by typing ``make html`` in the doc directory. You can also read the docs online at http://python-ly.readthedocs.org/.