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()