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 <[email protected]> a écrit :
>
> 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
>
#!/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()