On Fri, Dec 20, 2024 at 1:40 PM Knute Snortum <[email protected]> wrote:
> Since you're going to write a Python script, I figure I can share what
> I've done so far on mine.
>
I made a lot of improvements to my Python script (one of the advantages of
being retired) and I thought I'd share it, even if it never gets used. It
deals with:
* chords that don't have two notes in them (it leaves them alone)
* adding dotted length and articulations and slurs, etc. to each voice (but
stops if there is a space)
* deals with chords that aren't separated by spaces
* automatically changes \relative blocks to absolute
It certainly could be cleaner, but I'll share what I have now:
--
Knute Snortum
import argparse
import ly.docinfo
import ly.document
import ly.lex
import ly.lex.lilypond
import ly.pitch
import ly.pitch.rel2abs
# Get arguments
parser = argparse.ArgumentParser()
parser.add_argument("file", help="ly file to convert")
parser.add_argument("-f", "--first-pitch-absolute",
help="is the first note of the relative block an absolute pitch?",
action="store_false")
args = parser.parse_args()
# Open document
with open(args.file) as f:
txt = f.read()
doc = ly.document.Document(txt)
docInfo = ly.docinfo.DocInfo(doc)
found_relative = docInfo.find(r'\relative')
# Run rel2abs?
if found_relative != -1:
language = docInfo.language() or 'nederlands'
cursor = ly.document.Cursor(doc)
ly.pitch.rel2abs.rel2abs(cursor, language=language, first_pitch_absolute=args.first_pitch_absolute)
txt = cursor.document.plaintext()
# Main loop, break chords into two voices
# Assumes that chords start with the lowest note first
tokens = ly.lex.state("lilypond").tokens(txt)
for token in tokens:
# Start of chord
if isinstance(token, ly.lex.lilypond.ChordStart):
note_name = ''
notes = []
# Chord notes loop
for token in tokens:
# End of chord, write out the two voices
if isinstance(token, ly.lex.lilypond.ChordEnd):
notes.append(note_name)
# Chord doesn't have two notes, put it back together
if len(notes) != 2:
print(f"<{' '.join(notes)}>", end='')
else:
# Gather the length of the chord and any articulation, slurs, etc.
# FIXME doesn't deal with articulation etc. that is separated by a space
after = ''
hold = ''
startChordAgain = False
while True:
token = next(tokens)
# A new chord has started before we finished this one, set flag
if isinstance(token, ly.lex.lilypond.ChordStart):
startChordAgain = True
break
elif not isinstance(token, ly.lex._token.Space):
after += str(token)
else:
hold = str(token)
break
# Print the notes as two voices, higher first
# Assumes notes in chord go from low to high
print(f"<<{{ {notes[1]}{after} }} \\\\ {{ {notes[0]}{after} }}>>", end='')
print(hold, end='')
# Re-start the chord logic
if startChordAgain:
note_name = ''
notes = []
# Otherwise, break out of chord notes loop
else:
break
# Get note name and possible octave
elif isinstance(token, ly.lex.lilypond.Note):
note_name = str(token)
elif isinstance(token, ly.lex.lilypond.Octave):
note_name += str(token)
# Gather note and start a new one
elif isinstance(token, ly.lex._token.Space):
notes.append(note_name)
note_name = ''
# Everything else, leave alone
else:
print(token, end='')
print()