diff --git a/lilypond/score_template.ly b/lilypond/score_template.ly new file mode 100644 index 0000000..9a8aa0f --- /dev/null +++ b/lilypond/score_template.ly @@ -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} diff --git a/src/transcriber.py b/src/transcriber.py index 68ac044..3d98c83 100644 --- a/src/transcriber.py +++ b/src/transcriber.py @@ -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, )