Browse Source

Add missing transpose lilypond code, add lilypond template filename to properties and metadata dialog. Will be symlinked into the session dir automatically.

master
Nils 2 months ago
parent
commit
be9dcdc8cf
  1. 11
      CHANGELOG
  2. 89
      engine/lilypond.py
  3. 16
      engine/main.py
  4. 6
      engine/resources/lilypondTemplates/default.ly

11
CHANGELOG

@ -5,6 +5,17 @@ Two empty lines before the next entry.
External contributors notice at the end of the line: (LastName, FirstName / nick)
## 2022-10-15 2.2.0
Add more time signatures to the quick-insert dialog: 8/4, three variants of 7/8 and two variants of 5/4 and more. Also reorder and better labels.
Fix splitting of notes that were created by a previous split.
Various small fixes, like typos in variable names and wrong string quotes. Small things can crash as well.
Lilypond:
Add transposition of the whole score to properties and metadata dialog
Only set tempo markings in parenthesis when user provided a string like Allegro
Add lilypond template filename to properties and metadata dialog. Will be symlinked into the session dir automatically.
Overhaul Key Signature GUI dialog to indicate "Deviation" better, instead of absolute accidentals.
## 2022-07-15 2.1.0
New function: custom key signature for any combination.
Add area in the GUI to set initial key signature, metrical instructions and clefs, accessed through the track editor

89
engine/lilypond.py

@ -87,17 +87,44 @@ def lilyfy(string):
string = string.replace('"', '\\"')
return string
def fromTemplate(session, templateFile, data, meta, tempoStaff):
def fromTemplate(session, 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 "foo.ly" or
if the templateFile is None/"" the current filename+ly is used: "bar.lbj.ly"
Laborejo Markers in the template have the syntax %$$DATE$$ . That is pretty unique and additionaly a lilypond
comment.
meta is the actual score.metaData dictionary, not a copy. We use this to modify the template
path entry.
Laborejo Markers in the template have the syntax %$$DATE$$ .
That is pretty unique and additionally a lilypondcomment.
The template file is in metaData["template-file"]. The default is empty string,
which means we use the programs "default.ly" template.
For session management reasons this the user template must be a file in the session dir.
However, a system wide user library of lilypond export templates is very useful and convenient.
Thus we allow absolute paths as well. Or even relative and ~/ if the user wants. They will be
converted automatically to a symlink into the session dir and the absolute path will be
auto-replaced by the local filename to the symlink. (The absolute path will remain in metadata
and gui field until the file is found. Thus a typo will not lead to a broken symlink
creation)
If template file lookup fails for any reason we use the internal default.ly and log an error
message.
"""
#Load the template
templatePath = findTemplate(session, templateFile)
#Find and load the template
templateFile = meta["template-file"]
try:
templatePath = findTemplate(session, meta, templateFile)
except FileNotFoundError: #Fall back to default.py and try again
logger.error(f"User lilypond template {templateFile} not found. Will use the builtin default.ly instead.")
templatePath = findTemplate(session, meta, "") #If this fails something is indeed wrong and we let the error raise
except PermissionError: #Fall back to default.py and try again
logger.error(f"User lilypond template {templateFile} not readable. Will use the builtin default.ly instead.")
templatePath = findTemplate(session, meta, "") #If this fails something is indeed wrong and we let the error raise
#Now we know the templatePath exists and it is either default.ly or a file/link in our session dir.
with open(templatePath, 'r') as f:
templateString = f.read()
@ -109,27 +136,39 @@ def fromTemplate(session, templateFile, data, meta, tempoStaff):
templateString = templateString.replace("%$$VOICES$$", voicesString)
templateString = templateString.replace("%$$STRUCTURE$$", structureString)
templateString = templateString.replace("%$$TEMPOSTAFF$$", tempoStaff)
templateString = templateString.replace("%$$TRANSPOSITION$$", '\\transpose ' + " ".join(meta["transposition"].split()) ) #something like "c' f". Defaults to "c c ". Lilypond output without ""!
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)
else:
path = os.path.join(session.sessionPrefix, "template.laborejo2.ly") #the name is always the same because the sessionPrefix is the unqiue part
assert path.endswith(".ly")
if os.path.exists(path):
return path
else:
raise FileNotFoundError(templateFile)
def findTemplate(session, meta, templateFile:str)->str:
"""returns a path. checks for existence and read access through NSMclient functionality.
This will create a symlink in our session dir and always use this. It will also destructively
change the user metadata to this symlink file, if present."""
if not templateFile:
path = os.path.join(PATHS["share"], "lilypondTemplates", "default.ly")
return path #we don't need the checks below for our own file
#User provided a template file name or path
#Our NSM client can safely import any file. If we try to "import" a file already in our session dir nothing bad will happen, we just use the file.
assert session.nsmClient.importResource
logger.info("Trying to use user provided ly template: " + templateFile)
#This can throw FileNotFoundError or PermissionError, which we catch in the parent function. No need to check for ourselves again here.
#We get just the filename for symlinks in our session dir. Quickly check that and adjust to absolute path.
templateFile = os.path.expanduser(templateFile) # ~home
if os.path.basename(templateFile) == templateFile:
templateFile = os.path.join(session.sessionPrefix, templateFile)
#no need to check. even if that fails it will be caught safely below and reported to the log
path = session.nsmClient.importResource(templateFile) #it will always be an absolute path. Check the nsm INFO log.
meta["template-file"] = os.path.basename(path)
logger.info("Imported or re-used: " + path)
return path
def processData(data):
"""returns two strings. the first actual music data, VOICES, the second the structure and order,

16
engine/main.py

@ -46,7 +46,9 @@ class Data(template.engine.sequencer.Score):
#Lilypond Properties that might be used elsewhere as well, e.g. JACK Pretty Names
self.metaData = {key:"" for key in ("title", "subtitle", "dedication","composer","subsubtitle","instrument","meter","arranger", "poet","piece","opus","copyright","tagline", "subtext")}
self.metaData["metronome"] = True #show metronome in printout?
self.metaData["metronome"] = True #show metronome in printout? v2.1.0
self.metaData["transposition"] = "c c" # Whole score transposition for Lilypond. v2.2.0
self.metaData["template-file"] = "" # Lilypond Template file. If empty use default.ly . v2.2.0
self.currentMetronomeTrack = self.tracks[0] #A Laborejo Track, indepedent of currentTrack. The metronome is in self.metronome, set by the template Score.
self._processAfterInit()
@ -1058,7 +1060,9 @@ class Data(template.engine.sequencer.Score):
"""
tempoStaff = self.tempoTrack.lilypond() if "metronome" in self.metaData and self.metaData["metronome"] else ""
data = {track:track.lilypond() for track in self.tracks} #processed in the lilypond module
return fromTemplate(session = self.parentSession, templateFile = "default.ly", data = data, meta = self.metaData, tempoStaff = tempoStaff)
#the template file name is in metadata, but it can be empty or not existient
#From Template has direct access to the score metadata and WILL destructively modify it's own parameters, like the lilypond template entry.
return fromTemplate(session = self.parentSession, data = data, meta = self.metaData, tempoStaff = tempoStaff)
def getMidiInputNameAndUuid(self):
"""
@ -1093,6 +1097,14 @@ class Data(template.engine.sequencer.Score):
self.hiddenTracks = {Track.instanceFromSerializedData(parentData=self, serializedData=track):originalIndex for track, originalIndex in serializedData["hiddenTracks"]}
self.tempoTrack = TempoTrack.instanceFromSerializedData(serializedData["tempoTrack"], parentData = self)
self.metaData = serializedData["metaData"]
if not "metronome" in self.metaData: #2.1.0
self.metaData["metronome"] = True #replicate __init__ default
if not "transposition" in self.metaData: #2.2.0
self.metaData["transposition"] = "c c" #replicate __init__ default
if not "template-file" in self.metaData: #2.2.0
self.metaData["template-file"] = "" #replicate __init__ default
self.currentMetronomeTrack = self.tracks[serializedData["currentMetronomeTrackIndex"]]
self._processAfterInit()
return self

6
engine/resources/lilypondTemplates/default.ly

@ -95,7 +95,10 @@ tempoStaff = { %$$TEMPOSTAFF$$ }
%$$VOICES$$
\score{
\score {
%Transpose the entire score. Nothing happens when "c c"
%$$TRANSPOSITION$$
<<
%How the definitions are arranged. Only staffgroups (staff prefix) and staffs, merged as voices.
@ -103,6 +106,7 @@ tempoStaff = { %$$TEMPOSTAFF$$ }
%$$STRUCTURE$$
>>}
\markup{
%\column {
%$$SUBTEXT$$

Loading…
Cancel
Save