Hi Knute, Please let me first thank you so much for what you shared on this topic of converting chords to multivoices. Thanks to you, I discovered there exists a python lexer for lilypond files; this is really awesome!
On my side, I worked in a different direction, starting from your first script. I wanted to have a unique << { } \\ { } >> if possible and not repeat that at each chord. Here is where I am currently. Not yet perfect but on the following music: \times 4/6 { r16 <re fa>-.[( <fa sib>-. <sib re>-.-> <fa sib>-. <re fa>-.)] } I get what I expect: \times 4/6 { r16 << { fa-.[( sib-. re-.-> sib-. fa-.)] } \\ { re-.[( fa-. sib-.-> fa-. re-.)] } >> } I am not very familiar yet with the ly library so not sure this is really correctly done. I see that you worked on the relative/absolute issue. I have not the time right now to dig into that. I will certainly later but I still wanted to share what I did. Thanks, F Le dim. 22 déc. 2024 à 01:40, Knute Snortum <ksnor...@gmail.com> a écrit : > > 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 >
#!/usr/bin/python3 import argparse import ly.lex import ly.lex.lilypond import os.path import sys def flush_voices(voices): ''' Print all voices in reverse order in multivoice mode. voices[0] is the lower voice and is printed last voices[-1] is the higher voice and is printed first ''' if len(voices) > 0: print("<< {") for i in range(len(voices)): if i > 0: print("\n} \\\\ {", end = '\n') print(' ', end = '') voice = voices[len(voices) - 1 - i] for voice_token in voice: if isinstance(voice_token, ly.lex._token.Space): print(' ', end = '') else: print(voice_token, end = '') print("\n} >>") ### program starts here ### parser = argparse.ArgumentParser(description = 'In a Lilypond file, converts chords into multivoice mode.') parser.add_argument("file", help = "Lilypond file to convert") args = parser.parse_args() if not os.path.isfile(args.file): print(f"error: file '{args.file}' does not exist or is not a file") sys.exit(1) with open(args.file, 'r') as f: txt = f.read() s = ly.lex.state("lilypond") tokens = s.tokens(txt) # voices contains voices found in chords from lower [0] to higher [-1] # if, after a chord, a new chord is found, the new chord notes are appended to each corresponding voice # when something else than a chord is read, the voices are flushed in multivoice mode voices = [] pending = None for token in tokens: if isinstance(token, ly.lex.lilypond.ChordStart): # enter in a chord voice = [] # will contain the chord note of the voice and all additions after the chord voice_id = 0 # restart at voice 0 for inside_token in tokens: # read the chord notes if isinstance(inside_token, ly.lex.lilypond.ChordEnd): if len(voice) > 0: if len(voices) > voice_id: voices[voice_id] += voice else: voices += [ voice ] voice = [] voice_id = 0 break # assume a space separates notes if isinstance(inside_token, ly.lex._token.Space) and len(voice) > 0: if len(voices) > voice_id: voices[voice_id] += voice else: voices += [ voice ] voice = [] voice_id += 1 else: voice += [ inside_token ] # add tokens after the chords to each voice until a space is found for outside_token in tokens: for i in range(len(voices)): voices[i] += [ outside_token ] if isinstance(outside_token, ly.lex._token.Space): pending = outside_token break else: flush_voices(voices) if pending is not None: print(pending, end = '') pending = None voices = [] print(token, end = '') print()