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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
name: Name for the output
|
||||
template_path: Path to score template
|
||||
name: Name for the output (used as title)
|
||||
num_voices: Number of voices/staves to generate
|
||||
output_dir: Base output directory
|
||||
"""
|
||||
template = Path(template_path)
|
||||
if not template.exists():
|
||||
template_path = Path(output_dir) / name / "score_template.ly"
|
||||
if not template_path.exists():
|
||||
print(f"Error: Template not found: {template_path}")
|
||||
return False
|
||||
|
||||
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("{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:
|
||||
f.write(score_text)
|
||||
|
|
@ -550,7 +566,6 @@ def transcribe(
|
|||
chords,
|
||||
name,
|
||||
fundamental=55,
|
||||
template_path="lilypond/score_template.ly",
|
||||
output_dir="lilypond",
|
||||
generate_pdf_flag=True,
|
||||
):
|
||||
|
|
@ -560,22 +575,30 @@ def transcribe(
|
|||
chords: Chord data (list from output_chords.json or music_data format)
|
||||
name: Name for the output
|
||||
fundamental: Fundamental frequency in Hz
|
||||
template_path: Path to score template
|
||||
output_dir: Base output directory
|
||||
generate_pdf_flag: Whether to generate PDF
|
||||
|
||||
Returns:
|
||||
Dictionary with paths to generated files
|
||||
"""
|
||||
import shutil
|
||||
|
||||
if isinstance(chords[0][0], dict):
|
||||
music_data = output_chords_to_music_data(chords, fundamental)
|
||||
else:
|
||||
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)
|
||||
|
||||
if template_path and Path(template_path).exists():
|
||||
generate_score(name, template_path, output_dir)
|
||||
num_voices = len(music_data)
|
||||
generate_score(name, num_voices, output_dir)
|
||||
|
||||
result = {
|
||||
"parts_dir": str(Path(output_dir) / name / "includes"),
|
||||
|
|
@ -606,11 +629,6 @@ def main():
|
|||
parser.add_argument(
|
||||
"--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(
|
||||
"--lilypond-dir", default="lilypond", help="Base LilyPond output directory"
|
||||
)
|
||||
|
|
@ -636,7 +654,6 @@ def main():
|
|||
chords,
|
||||
args.name,
|
||||
fundamental=args.fundamental,
|
||||
template_path=args.template,
|
||||
output_dir=args.lilypond_dir,
|
||||
generate_pdf_flag=not args.no_pdf,
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue