Make LilyPond template dynamic - title and staves generated from parameters
This commit is contained in:
parent
306de8b5c2
commit
6bf97e5abe
112
lilypond/score_template.ly
Normal file
112
lilypond/score_template.ly
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
\version "2.24.1"
|
||||||
|
|
||||||
|
\paper {
|
||||||
|
#(set-paper-size "a4" 'portrait)
|
||||||
|
top-margin = 1 \cm
|
||||||
|
bottom-margin = 1 \cm
|
||||||
|
left-margin = 2 \cm
|
||||||
|
ragged-bottom = ##t
|
||||||
|
|
||||||
|
top-system-spacing =
|
||||||
|
#'((basic-distance . 15 )
|
||||||
|
(minimum-distance . 15 )
|
||||||
|
(padding . 0 )
|
||||||
|
(stretchability . 0))
|
||||||
|
|
||||||
|
system-system-spacing =
|
||||||
|
#'((basic-distance . 30 )
|
||||||
|
(minimum-distance . 30 )
|
||||||
|
(padding . 0 )
|
||||||
|
(stretchability . 0))
|
||||||
|
|
||||||
|
last-bottom-spacing =
|
||||||
|
#'((basic-distance . 10 )
|
||||||
|
(minimum-distance . 10 )
|
||||||
|
(padding . 0 )
|
||||||
|
(stretchability . 0))
|
||||||
|
|
||||||
|
%systems-per-page = 4
|
||||||
|
first-page-number = 1
|
||||||
|
print-first-page-number = ##t
|
||||||
|
|
||||||
|
print-page-number = ##t
|
||||||
|
oddHeaderMarkup = \markup { \fill-line { \line { \unless \on-first-page {\pad-markup #2 { \concat {\italic {"{NAME}"}}}}}}}
|
||||||
|
evenHeaderMarkup = \markup { \fill-line { \line { \unless \on-first-page {\pad-markup #2 { \concat {\italic {"{NAME}"}}}}}}}
|
||||||
|
oddFooterMarkup = \markup { \fill-line {
|
||||||
|
\concat {
|
||||||
|
"-"
|
||||||
|
\fontsize #1.5
|
||||||
|
\fromproperty #'page:page-number-string
|
||||||
|
"-"}}}
|
||||||
|
evenFooterMarkup = \markup { \fill-line {
|
||||||
|
\concat {
|
||||||
|
"-"
|
||||||
|
\fontsize #1.5
|
||||||
|
\fromproperty #'page:page-number-string
|
||||||
|
"-"}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
\header {
|
||||||
|
title = \markup { \italic {"{NAME}"}}
|
||||||
|
composer = \markup \right-column {"michael winter" "(cdmx and schloss solitude; 2024)"}
|
||||||
|
poet = ""
|
||||||
|
tagline = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
#(set-global-staff-size 11)
|
||||||
|
|
||||||
|
\layout {
|
||||||
|
indent = 0.0\cm
|
||||||
|
line-width = 17.5\cm
|
||||||
|
ragged-last = ##f
|
||||||
|
ragged-right = ##f
|
||||||
|
|
||||||
|
\context {
|
||||||
|
\Score
|
||||||
|
\override BarNumber.stencil = #(make-stencil-circler 0.1 0.25 ly:text-interface::print)
|
||||||
|
\override Stem.stemlet-length = #0.75
|
||||||
|
%proportionalNotationDuration = #(ly:make-moment 1/16)
|
||||||
|
\remove "Separating_line_group_engraver"
|
||||||
|
\override RehearsalMark.self-alignment-X = #-1
|
||||||
|
\override RehearsalMark.Y-offset = #10
|
||||||
|
\override RehearsalMark.X-offset = #-8
|
||||||
|
%\override RehearsalMark.outside-staff-priority = #0
|
||||||
|
\override SpacingSpanner.base-shortest-duration = #(ly:make-moment 1/32)
|
||||||
|
%\override Stem.stencil = ##f
|
||||||
|
%\override BarLine.stencil = ##f
|
||||||
|
}
|
||||||
|
\context {
|
||||||
|
\Staff
|
||||||
|
|
||||||
|
\override VerticalAxisGroup.staff-staff-spacing =
|
||||||
|
#'((basic-distance . 20 )
|
||||||
|
(minimum-distance . 20 )
|
||||||
|
(padding . 0 )
|
||||||
|
(stretchability . 0))
|
||||||
|
|
||||||
|
\override VerticalAxisGroup.default-staff-staff-spacing =
|
||||||
|
#'((basic-distance . 20 )
|
||||||
|
(minimum-distance . 20 )
|
||||||
|
(padding . 0 )
|
||||||
|
(stretchability . 0))
|
||||||
|
\override TextScript.staff-padding = #2
|
||||||
|
%\override TextScript.self-alignment-X = #0
|
||||||
|
}
|
||||||
|
\context {
|
||||||
|
\StaffGroup
|
||||||
|
\name "SemiStaffGroup"
|
||||||
|
\consists "Span_bar_engraver"
|
||||||
|
\override SpanBar.stencil =
|
||||||
|
#(lambda (grob)
|
||||||
|
(if (string=? (ly:grob-property grob 'glyph-name) "|")
|
||||||
|
(set! (ly:grob-property grob 'glyph-name) ""))
|
||||||
|
(ly:span-bar::print grob))
|
||||||
|
}
|
||||||
|
\context {
|
||||||
|
\Score
|
||||||
|
\accepts SemiStaffGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{STAVES}
|
||||||
|
|
@ -469,24 +469,40 @@ def output_chords_to_music_data(chords, fundamental=55, chord_duration=4):
|
||||||
return music_data
|
return music_data
|
||||||
|
|
||||||
|
|
||||||
def generate_score(name, template_path, output_dir="lilypond"):
|
def generate_score(name, num_voices, output_dir="lilypond"):
|
||||||
"""Generate full score .ly file from template.
|
"""Generate full score .ly file from template.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Name for the output
|
name: Name for the output (used as title)
|
||||||
template_path: Path to score template
|
num_voices: Number of voices/staves to generate
|
||||||
output_dir: Base output directory
|
output_dir: Base output directory
|
||||||
"""
|
"""
|
||||||
template = Path(template_path)
|
template_path = Path(output_dir) / name / "score_template.ly"
|
||||||
if not template.exists():
|
if not template_path.exists():
|
||||||
print(f"Error: Template not found: {template_path}")
|
print(f"Error: Template not found: {template_path}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
score_path = Path(output_dir) / name / f"{name}.ly"
|
score_path = Path(output_dir) / name / f"{name}.ly"
|
||||||
score_text = template.read_text()
|
score_text = template_path.read_text()
|
||||||
|
|
||||||
score_text = score_text.replace("{NAME}", name)
|
score_text = score_text.replace("{NAME}", name)
|
||||||
score_text = score_text.replace("{COMPOSER}", "Michael Winter")
|
|
||||||
|
voice_names = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII"]
|
||||||
|
staves = ""
|
||||||
|
for i in range(num_voices):
|
||||||
|
v_name = voice_names[i]
|
||||||
|
staves += f'''
|
||||||
|
\\new Staff = "{v_name}" \\with {{
|
||||||
|
instrumentName = "{v_name}"
|
||||||
|
shortInstrumentName = "{v_name}"
|
||||||
|
midiInstrument = #"clarinet"
|
||||||
|
}}
|
||||||
|
{{
|
||||||
|
\\include "includes/part_{v_name}.ly"
|
||||||
|
}}
|
||||||
|
'''
|
||||||
|
|
||||||
|
score_text = score_text.replace("{STAVES}", staves)
|
||||||
|
|
||||||
with open(score_path, "w") as f:
|
with open(score_path, "w") as f:
|
||||||
f.write(score_text)
|
f.write(score_text)
|
||||||
|
|
@ -550,7 +566,6 @@ def transcribe(
|
||||||
chords,
|
chords,
|
||||||
name,
|
name,
|
||||||
fundamental=55,
|
fundamental=55,
|
||||||
template_path="lilypond/score_template.ly",
|
|
||||||
output_dir="lilypond",
|
output_dir="lilypond",
|
||||||
generate_pdf_flag=True,
|
generate_pdf_flag=True,
|
||||||
):
|
):
|
||||||
|
|
@ -560,22 +575,30 @@ def transcribe(
|
||||||
chords: Chord data (list from output_chords.json or music_data format)
|
chords: Chord data (list from output_chords.json or music_data format)
|
||||||
name: Name for the output
|
name: Name for the output
|
||||||
fundamental: Fundamental frequency in Hz
|
fundamental: Fundamental frequency in Hz
|
||||||
template_path: Path to score template
|
|
||||||
output_dir: Base output directory
|
output_dir: Base output directory
|
||||||
generate_pdf_flag: Whether to generate PDF
|
generate_pdf_flag: Whether to generate PDF
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with paths to generated files
|
Dictionary with paths to generated files
|
||||||
"""
|
"""
|
||||||
|
import shutil
|
||||||
|
|
||||||
if isinstance(chords[0][0], dict):
|
if isinstance(chords[0][0], dict):
|
||||||
music_data = output_chords_to_music_data(chords, fundamental)
|
music_data = output_chords_to_music_data(chords, fundamental)
|
||||||
else:
|
else:
|
||||||
music_data = chords
|
music_data = chords
|
||||||
|
|
||||||
|
output_path = Path(output_dir) / name
|
||||||
|
output_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
template_source = Path(__file__).parent.parent / "lilypond" / "score_template.ly"
|
||||||
|
if template_source.exists():
|
||||||
|
shutil.copy(template_source, output_path / "score_template.ly")
|
||||||
|
|
||||||
generate_parts(music_data, name, output_dir)
|
generate_parts(music_data, name, output_dir)
|
||||||
|
|
||||||
if template_path and Path(template_path).exists():
|
num_voices = len(music_data)
|
||||||
generate_score(name, template_path, output_dir)
|
generate_score(name, num_voices, output_dir)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"parts_dir": str(Path(output_dir) / name / "includes"),
|
"parts_dir": str(Path(output_dir) / name / "includes"),
|
||||||
|
|
@ -606,11 +629,6 @@ def main():
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--fundamental", type=float, default=55, help="Fundamental frequency in Hz"
|
"--fundamental", type=float, default=55, help="Fundamental frequency in Hz"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--template",
|
|
||||||
default="lilypond/score_template.ly",
|
|
||||||
help="LilyPond template path",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--lilypond-dir", default="lilypond", help="Base LilyPond output directory"
|
"--lilypond-dir", default="lilypond", help="Base LilyPond output directory"
|
||||||
)
|
)
|
||||||
|
|
@ -636,7 +654,6 @@ def main():
|
||||||
chords,
|
chords,
|
||||||
args.name,
|
args.name,
|
||||||
fundamental=args.fundamental,
|
fundamental=args.fundamental,
|
||||||
template_path=args.template,
|
|
||||||
output_dir=args.lilypond_dir,
|
output_dir=args.lilypond_dir,
|
||||||
generate_pdf_flag=not args.no_pdf,
|
generate_pdf_flag=not args.no_pdf,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue