Source code for ly.pitch.rel2abs

# 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


[docs]def rel2abs(cursor, language="nederlands", first_pitch_absolute=False): """Converts pitches from relative to absolute. language: language to start reading pitch names in first_pitch_absolute: if True, the first pitch of a \\relative expression is regarded as absolute, when no starting pitch was given. This mimics the behaviour of LilyPond >= 2.18. (In fact, the starting pitch is then assumed to be f.) If False, the starting pitch, when not given, is assumed to be c' (LilyPond < 2.18 behaviour). """ 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) elif first_pitch_absolute: lastPitch = ly.pitch.Pitch.f0() else: lastPitch = ly.pitch.Pitch.c1() # remove the \relative <pitch> 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