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

Reply via email to