Source code for ly.pitch.abs2rel

# 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


[docs]def abs2rel(cursor, language="nederlands", startpitch=True, first_pitch_absolute=False): """Converts pitches from absolute to relative. language: language to start reading pitch names in startpitch: if True, write a starting pitch before the opening bracket of a relative expression. first_pitch_absolute: this option only makes sense when startpitch is False. If first_pitch_absolute is True, the first pitch of a \\relative expression is written as absolute. This mimics the behaviour of LilyPond >= 2.18. (In fact, the starting pitch is then assumed to be f.) If False, the first pitch is written as relative to c' (LilyPond < 2.18 behaviour). Existing \\relative expressions are not changed. """ 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: if startpitch: 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)) else: if first_pitch_absolute: lastPitch = ly.pitch.Pitch.f0() else: lastPitch = ly.pitch.Pitch.c1() document[pos:pos] = "\\relative " p = t.copy() t.makeRelative(lastPitch) pitches.write(t) lastPitch = p # remember the first pitch of a chord if chord == []: chord.append(p)