On Fri, Dec 20, 2024 at 1:40 PM Knute Snortum <ksnor...@gmail.com> 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()

Reply via email to