#!/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()