You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
6.4 KiB

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
Copyright 2021, Nils Hilbricht, Germany ( )
This file is part of the Laborejo Software Suite ( ),
Laborejo2 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <>.
"""Use generated music data and build a complete lilypond file from it"""
import logging; logger = logging.getLogger(__name__);"import")
#Standard Library Modules
import os.path
from datetime import date
import subprocess
from tempfile import gettempdir
#Third Party Modules
#Template Modules
from template.start import PATHS
#Our modules
da = date.fromordinal(730920) # 730920th day after 1. 1. 0001
def saveAsLilypond(score, absoluteFilePath = None):
#absoluteFilePath = self.absoluteFilePath + ".ly" if (not absoluteFilePath) else absoluteFilePath
assert absoluteFilePath.endswith(".ly")
result = score.lilypond()
if result:
with open(absoluteFilePath, "w", encoding="utf-8") as f:
return absoluteFilePath
def saveAsLilypondPDF(score, openPDF = False):
tempfile = os.path.join(gettempdir(), str(id(score)) + ".ly")
exportedLilypond = saveAsLilypond(score, tempfile)
ret ="lilypond --output={} {}".format(exportedLilypond, exportedLilypond), shell=True) #suffix to output added by lilypond
if ret == 0 and openPDF:
subprocess.Popen("xdg-open {}.pdf".format(exportedLilypond), shell=True)
def stringToCharsOnlyString(string):
def num2word(n):
"""Take a number, return a str.
Lilypond does not allow numbers in variable names"""
return ''.join(chr(ord(c)+17) for c in str(n))
def num2wordForIterations(stringOrNum):
if stringOrNum in [1,2,3,4,5,6,7,8,9,0] or stringOrNum in "1234567890":
return num2word(int(stringOrNum))
elif stringOrNum in ("-", "_"):
return ""
return stringOrNum
string = string.replace(" ", "_")
return "".join([num2wordForIterations(c) for c in string])
def lilyfy(string):
return string
def fromTemplate(session, templateFile, data, meta, tempoStaff):
"""Returns a string built from already ly-exported track data and a lilypond template.
Called by score.lilypond(), which is called by session.lilypond().
A template is either one of the built-ins "" or
if the templateFile is None/"" the current filename+ly is used: ""
Laborejo Markers in the template have the syntax %$$DATE$$ . That is pretty unique and additionaly a lilypond
#Load the template
templatePath = findTemplate(session, templateFile)
with open(templatePath, 'r') as f:
templateString =
templateString = templateString.replace("%$$DATE$$","%A %d. %B %Y")) #The current date
templateString = templateString.replace("%$$FILENAME$$", session.sessionPrefix)
templateString = templateString.replace("%$$HEADER$$", processMeta(meta))
templateString = templateString.replace("%$$SUBTEXT$$", lilyfy(meta["subtext"]))
voicesString, structureString = processData(data)
templateString = templateString.replace("%$$VOICES$$", voicesString)
templateString = templateString.replace("%$$STRUCTURE$$", structureString)
templateString = templateString.replace("%$$TEMPOSTAFF$$", tempoStaff)
return templateString
def findTemplate(session, templateFile:str=None)->str:
"""returns a path. checks for existence.
There are two options:
- Use a standard template in SHARE/lilypondTemplates
- Use a template that belongs to the save file.
The one that belongs to the save file needs to be created by hand by the user.
If not we throw a FileNotFoundError"""
if templateFile:
path = os.path.join(PATHS["share"], "lilypondTemplates", templateFile)
path = os.path.join(session.sessionPrefix, "") #the name is always the same because the sessionPrefix is the unqiue part
assert path.endswith(".ly")
if os.path.exists(path):
return path
raise FileNotFoundError(templateFile)
def processData(data):
"""returns two strings. the first actual music data, VOICES, the second the structure and order,
def voice(track, lilypondTrack):
""" tgliHJuGCFAIICICBEHBJABHIJE= { \key c \major <c'>4 <e'>4 <g'>4 <c''>4 \bar "|."} """
return "{} = {}".format(stringToCharsOnlyString(, "{ " + lilypondTrack + " }")
def structure(track, lilypondTrack):
""" \new Staff = "tgliHJuGCFAIICICBEHBJABHIJE_Staff" << \\new Voice = "tgliHJuGCFAIICICBEHBJABHIJE" \tgliHJuGCFAIICICBEHBJABHIJE >> %EndTrack """
name = stringToCharsOnlyString(
instruments = '\\with {{ \n instrumentName = #"{}" \n shortInstrumentName = #"{}" }}'.format(lilyfy(track.initialInstrumentName), lilyfy(track.initialShortInstrumentName))
mergeTempo = "\\new Voice = \"Tempo\" \\tempoStaff"
return """\\new Staff = "{}_Staff" {} << {} \\new Voice = "{}" \{} >> %EndTrack""".format(name, instruments, mergeTempo, name, name)
voices = "\n ".join(voice(track, lilypondTrack) for track, lilypondTrack in data.items() if lilypondTrack)
structure = "\n ".join(structure(track, lilypondTrack) for track, lilypondTrack in data.items() if lilypondTrack)
return voices, structure
def processMeta(meta):
"""returns a string with lilypond header data like title and composer"""
def valueMarkup(value):
value = value.strip()
if value:
if value.startswith("\\markup"):
return value
return "\"{}\"".format(value) #results in "value"
return "##f"
metaString = "\n ".join(key + " = " + valueMarkup(value) for key, value in meta.items())
return metaString