Author SHA1 Message Date
tux 39d9e8c0d5 use lilypond 2.27 and be compatibel to 2.26 2026-10-22 12:54:22 +02:00
tux dfd72cd64d layout size for inline-scores 2026-10-22 11:59:27 +02:00
tux d0db6fae69 finetuning chord lyrics engraver 2026-10-22 11:47:48 +02:00
tux 2359a7c5ae Mehrere Lieder auf eine Seite 2026-10-11 23:20:40 +02:00
zuk 809100b2f3 define rehearsalMarkFormatter 2026-08-28 13:11:47 +02:00
12 changed files with 404 additions and 110 deletions

No files matched your search

+3
View File
@@ -33,9 +33,11 @@
#(define AUTHOR_DATA (if (defined? 'AUTHOR_DATA) AUTHOR_DATA (parse-yml-file (song-includes-data-path "authors.yml")))) #(define AUTHOR_DATA (if (defined? 'AUTHOR_DATA) AUTHOR_DATA (parse-yml-file (song-includes-data-path "authors.yml"))))
#(define SONG_DATA (if (defined? 'SONG_DATA) SONG_DATA (parse-yml-file (song-includes-data-path "songs.yml")))) #(define SONG_DATA (if (defined? 'SONG_DATA) SONG_DATA (parse-yml-file (song-includes-data-path "songs.yml"))))
\include #(if (ly:version? < '(2 27 0)) "compat_lilypond_2_26.ily" "../void.ily")
\include "merge_rests_engraver_override.ily" \include "merge_rests_engraver_override.ily"
\include "basic_format_and_style_settings.ily" \include "basic_format_and_style_settings.ily"
\include "eps_file_from_song_dir.ily" \include "eps_file_from_song_dir.ily"
\include "embedded_songs.ily"
\include "title_with_category_images.ily" \include "title_with_category_images.ily"
\include "chord_settings.ily" \include "chord_settings.ily"
\include "chordpro.ily" \include "chordpro.ily"
@@ -54,6 +56,7 @@ HEADER = {}
MUSIC = {} MUSIC = {}
TEXT = \markuplist {} TEXT = \markuplist {}
TEXT_PAGES = #f TEXT_PAGES = #f
EMBEDDED_SONGS = #'()
verseChords = {} verseChords = {}
firstVoice = {} firstVoice = {}
@@ -58,6 +58,7 @@ generalLayout = \layout {
\RemoveEmptyStaves \RemoveEmptyStaves
\override VerticalAxisGroup.remove-first = ##t \override VerticalAxisGroup.remove-first = ##t
\numericTimeSignature \numericTimeSignature
rehearsalMarkFormatter = #format-mark-box-alphabet
\overrideTimeSignatureSettings \overrideTimeSignatureSettings
4/4 % timeSignatureFraction 4/4 % timeSignatureFraction
1/4 % baseMomentFraction 1/4 % baseMomentFraction
@@ -90,6 +91,22 @@ generalLayout = \layout {
((custom-size (ly:output-def-lookup base-layout 'size #f))) ((custom-size (ly:output-def-lookup base-layout 'size #f)))
(if custom-size (layout-set-staff-size custom-size)))) (if custom-size (layout-set-staff-size custom-size))))
% Scores in Markups (z.B. inline-score) kopieren ihr \layout schon beim Parsen,
% also bevor customized-layout greift. Deshalb nachträglich anpassen.
#(define (customize-embedded-score-layouts! obj)
(cond
((ly:score? obj)
(for-each
(lambda (def)
(let ((custom-size (ly:output-def-lookup def 'size #f))
(scope (ly:output-def-scope def)))
(if custom-size
(layout-set-absolute-staff-size-in-module scope (* custom-size (eval 'pt scope))))))
(ly:score-output-defs obj)))
((pair? obj)
(customize-embedded-score-layouts! (car obj))
(customize-embedded-score-layouts! (cdr obj)))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% kleine Helferlein: %%% kleine Helferlein:
@@ -138,50 +155,83 @@ romanStanza =
#(define-music-function (parser location) () #(define-music-function (parser location) ()
#{ \override StanzaNumber.style = #'roman #}) #{ \override StanzaNumber.style = #'roman #})
#(define (number-or-number-list? x) (or (number? x) (number-list? x)))
% "1,4" -> (1 4); Einträge, die keine Zahl sind, bleiben Strings ("1b,2b" -> ("1b" "2b"))
#(define (stanza-numbers-from-string str)
(map (lambda (s) (or (string->number s) s))
(remove string-null? (map string-trim-both (string-split str #\,)))))
% Kein number? hier, sonst kann der Parser 1,4 nicht mehr als Liste lesen
#(define (stanza-numbers? x) (or (number-list? x) (string? x)))
override-stanza = override-stanza =
#(define-music-function (parser location stanzanumbers) (number-list?) #(define-music-function (stanzanumbers) (number-or-number-list?)
#{ #{
\once \override StanzaNumber.details.custom-stanzanumber-override = #stanzanumbers \once \override StanzaNumber.details.custom-stanzanumber-override = #(if (number? stanzanumbers) (list stanzanumbers) stanzanumbers)
#} #}
) )
#(define (handle-stanza-numbers context numbers number-formater) #(define (romanize-stanza-numbers numbers style)
(let* ((stanzanumbers (ly:assoc-get 'custom-stanzanumber-override (ly:assoc-get 'details (ly:context-grob-definition context 'StanzaNumber) '()) numbers)) (if (eq? style 'roman)
(stanza-style (ly:assoc-get 'style (ly:context-grob-definition context 'StanzaNumber))) (map (lambda (n) (if (number? n) (format #f "~@r" n) n)) numbers)
(roman-format (lambda (stanzanumber) (format #f "~@r" stanzanumber)))) numbers))
(ly:context-set-property! context 'stanza
(make-pad-left-markup 1
(number-formater
(if (eq? stanza-style 'roman)
(map roman-format stanzanumbers)
stanzanumbers))))))
#(define (stanza . stanzanumbers) #(define (format-stanza-numbers numbers style)
(string-join
(map (lambda (n) (format #f stanzaFormat n)) (romanize-stanza-numbers numbers style))
", "))
% Strophennummer, die erst beim Setzen aus den Eigenschaften des StanzaNumber-Grobs gebaut wird
% (details.custom-stanza-type, details.custom-stanza-numbers, style, details.custom-inline-text).
% So können \fstanza, \ref, \bridge und \repStartWithTag im selben Zeitschritt kombiniert werden.
#(define-markup-command (formatted-stanza layout props) ()
#:properties ((details '()) (style #f))
(let* ((realstanza (ly:assoc-get 'custom-realstanza details #f))
(type (ly:assoc-get 'custom-stanza-type details 'verse))
(numbers (ly:assoc-get 'custom-stanzanumber-override details
(ly:assoc-get 'custom-stanza-numbers details '())))
(inline-text (ly:assoc-get 'custom-inline-text details #f))
(inline-direction (ly:assoc-get 'custom-inline-direction details CENTER))
(rep-start (and inline-text (eqv? inline-direction LEFT)))
(roman-numbers (romanize-stanza-numbers numbers style))
(stanza-stencil
(and realstanza
(case type
((ref) ((ly:output-def-lookup layout 'refMarkupFormatter) layout props roman-numbers))
((bridge) ((ly:output-def-lookup layout 'bridgeMarkupFormatter) layout props roman-numbers))
(else (interpret-markup layout props (format-stanza-numbers numbers style))))))
(padded-stanza-stencil
(and stanza-stencil
(interpret-markup layout props (make-pad-left-markup 1 (make-stencil-markup stanza-stencil))))))
(if rep-start
(interpret-markup layout props
(make-concat-markup
(append
(if padded-stanza-stencil (list (make-stencil-markup padded-stanza-stencil) (make-hspace-markup 1)) '())
(list (make-override-markup '(font-series . normal) inline-text)))))
(or padded-stanza-stencil empty-stencil))))
#(define (formatted-stanza-music type stanzanumbers)
#{ #{
\once \override StanzaNumber.details.custom-realstanza = ##t % set this to signal that there is a real stanza and no repeat signs \once \override StanzaNumber.details.custom-realstanza = ##t % set this to signal that there is a real stanza and no repeat signs
\once \override StanzaNumber.details.custom-stanza-type = #'verse \once \override StanzaNumber.details.custom-stanza-type = #type
\once \override StanzaNumber.details.custom-stanza-numbers = #stanzanumbers \once \override StanzaNumber.details.custom-stanza-numbers = #stanzanumbers
\applyContext \stanza \markup \formatted-stanza
#(lambda (context) #})
(handle-stanza-numbers context stanzanumbers
(lambda (numbers) (string-join (map (lambda (n) (format #f stanzaFormat n)) numbers) ", ")))) % \fstanza 1 -> "1."
#} % \fstanza 1,4 -> "1., 4." (nur im Notenmodus, z.B. \chordverse { \fstanza 1,4 } ...)
) % \fstanza "1,4" -> "1., 4." (auch im \lyricmode, dort versteht LilyPond 1,4 nicht als Liste)
fstanza =
#(define-music-function (stanzanumbers) (stanza-numbers?)
(formatted-stanza-music 'verse
(if (string? stanzanumbers) (stanza-numbers-from-string stanzanumbers) stanzanumbers)))
ref = ref =
#(define-music-function (stanzanumbers lyrics) ((number-list? (list)) ly:music?) #(define-music-function (stanzanumbers lyrics) ((number-list? (list)) ly:music?)
#{ \lyricmode { #{ \lyricmode {
\once \override StanzaNumber.details.custom-realstanza = ##t % set this to signal that there is a real stanza and no repeat signs #(formatted-stanza-music 'ref stanzanumbers)
\once \override StanzaNumber.details.custom-stanza-type = #'ref
\once \override StanzaNumber.details.custom-stanza-numbers = #stanzanumbers
\applyContext
#(lambda (context)
(handle-stanza-numbers context stanzanumbers
(lambda (numbers)
(make-on-the-fly-markup
(lambda (layout props m)
((ly:output-def-lookup layout 'refMarkupFormatter) layout props numbers))
(make-null-markup)))))
#lyrics #lyrics
} }
#} #}
@@ -190,43 +240,24 @@ ref =
bridge = bridge =
#(define-music-function (stanzanumbers lyrics) ((number-list? (list)) ly:music?) #(define-music-function (stanzanumbers lyrics) ((number-list? (list)) ly:music?)
#{ \lyricmode { #{ \lyricmode {
\once \override StanzaNumber.details.custom-realstanza = ##t % set this to signal that there is a real stanza and no repeat signs #(formatted-stanza-music 'bridge stanzanumbers)
\once \override StanzaNumber.details.custom-stanza-type = #'bridge
\once \override StanzaNumber.details.custom-stanza-numbers = #stanzanumbers
\applyContext
#(lambda (context)
(handle-stanza-numbers context stanzanumbers
(lambda (numbers)
(make-on-the-fly-markup
(lambda (layout props m)
((ly:output-def-lookup layout 'bridgeMarkupFormatter) layout props numbers))
(make-null-markup)))))
#lyrics #lyrics
} }
#} #}
) )
% prints a repStart Sign as stanza if the tag 'repeats is kept. % prints a repStart Sign as stanza if the tag 'repeats is kept.
% if there was a stanza already set by the stanza function with StanzaNumber.details.custom-realstanza = ##t we set that also as stanza. % if there is a stanza set by \fstanza in the same moment, \formatted-stanza prints both.
% Sets custom-inline-text for ChordPro export so it can collect the repeat sign separately % Sets custom-inline-text for ChordPro export so it can collect the repeat sign separately
repStartWithTag = \lyricmode { repStartWithTag =
#(define-music-function () ()
#{ \lyricmode {
\tag #'repeats { \tag #'repeats {
\once \override StanzaNumber.details.custom-inline-text = \repStart \once \override StanzaNumber.details.custom-inline-text = \repStart
\once \override StanzaNumber.details.custom-inline-direction = #LEFT \once \override StanzaNumber.details.custom-inline-direction = #LEFT
\applyContext \stanza \markup \formatted-stanza
#(lambda (context)
(let ((lastStanza (ly:context-property context 'stanza))
(printLastStanza (ly:assoc-get 'custom-realstanza (ly:assoc-get 'details (ly:context-grob-definition context 'StanzaNumber) '()) #f))
(stanzaFontSeries (ly:assoc-get 'font-series (ly:context-grob-definition context 'StanzaNumber) 'normal)))
(ly:context-set-property! context 'stanza
(make-concat-markup
(if printLastStanza
(list (make-override-markup `(font-series . ,stanzaFontSeries) lastStanza) (make-hspace-markup 1) repStart)
(list repStart)
)))))
\once \override StanzaNumber.font-series = #'normal
} }
} } #})
repStopWithTag = \lyricmode { repStopWithTag = \lyricmode {
\tag #'repeats { \tag #'repeats {
@@ -234,19 +265,19 @@ repStopWithTag = \lyricmode {
\once \override StanzaNumber.details.custom-inline-direction = #RIGHT \once \override StanzaNumber.details.custom-inline-direction = #RIGHT
\once \override StanzaNumber.font-series = #'normal \once \override StanzaNumber.font-series = #'normal
\once \override StanzaNumber.direction = 1 \once \override StanzaNumber.direction = 1
\set stanza = \markup { \pad-x-right #1 \repStop } \stanza \markup { \pad-x-right #1 \repStop }
} }
} }
rightHyphen = \lyricmode { rightHyphen = \lyricmode {
\once \override StanzaNumber.font-series = #'normal \once \override StanzaNumber.font-series = #'normal
\once \override StanzaNumber.direction = 1 \once \override StanzaNumber.direction = 1
\set stanza = "-" \stanza "-"
} }
leftHyphen = \lyricmode { leftHyphen = \lyricmode {
\once \override StanzaNumber.font-series = #'normal \once \override StanzaNumber.font-series = #'normal
\set stanza = "-" \stanza "-"
} }
multiVerseSkips = multiVerseSkips =
@@ -312,7 +343,7 @@ rufWithMarkup =
\lyricmode { \lyricmode {
\once \override StanzaNumber.font-series = #'normal \once \override StanzaNumber.font-series = #'normal
\once \override StanzaNumber.direction = 1 \once \override StanzaNumber.direction = 1
\set stanza = #text \stanza #text
} }
#}) #})
ruf = ruf =
+1 -1
View File
@@ -155,7 +155,7 @@ Ensure_first_chord_after_line_break_printed_engraver =
; the current chord handling implementation in lilypond uses 'begin-of-line-visible to mark repeated chords ; the current chord handling implementation in lilypond uses 'begin-of-line-visible to mark repeated chords
(ly:grob-set-property! grob 'begin-of-line-visible #f)) (ly:grob-set-property! grob 'begin-of-line-visible #f))
(set! last-system-column-rank current-system-column-rank) (set! last-system-column-rank current-system-column-rank)
(ly:chord-name::after-line-breaking grob) (item::enforce-musical-break-visibility grob)
))))))) )))))))
% If you add this engraver to ChordNames Context chords get only printed on chordchanges and not at newline. % If you add this engraver to ChordNames Context chords get only printed on chordchanges and not at newline.
+11 -4
View File
@@ -319,14 +319,21 @@
;; Use context property if available, otherwise grob property ;; Use context property if available, otherwise grob property
(stanza-markup (or stanza-text-from-context stanza-text-from-grob)) (stanza-markup (or stanza-text-from-context stanza-text-from-grob))
;; For ref/bridge, always use format-chordpro-stanza-label ;; For ref/bridge, always use format-chordpro-stanza-label
;; For verse, extract from markup (includes roman numerals if \romanStanza was used) ;; For verses set by \fstanza, format the numbers (roman numerals if \romanStanza was used)
(stanza-text (if (or (eq? stanza-type 'ref) (eq? stanza-type 'bridge)) ;; Otherwise, extract from markup
(format-chordpro-stanza-label stanza-type stanza-numbers) (stanza-text (cond
((or (eq? stanza-type 'ref) (eq? stanza-type 'bridge))
(format-chordpro-stanza-label stanza-type stanza-numbers))
(custom-realstanza
(format-stanza-numbers
(ly:assoc-get 'custom-stanzanumber-override details stanza-numbers)
(ly:grob-property grob 'style #f)))
(else
(if (markup? stanza-markup) (if (markup? stanza-markup)
(markup->string stanza-markup) (markup->string stanza-markup)
(if stanza-markup (if stanza-markup
(format #f "~a" stanza-markup) (format #f "~a" stanza-markup)
#f)))) #f)))))
(existing (assoc this-verse-index chordpro-stanza-numbers)) (existing (assoc this-verse-index chordpro-stanza-numbers))
(moment (ly:context-current-moment (ly:translator-context source-engraver))) (moment (ly:context-current-moment (ly:translator-context source-engraver)))
(direction (ly:grob-property grob 'direction CENTER))) (direction (ly:grob-property grob 'direction CENTER)))
@@ -0,0 +1,11 @@
% Abwärtskompatibilität zu LilyPond 2.26.
% Wird von all.ily nur eingebunden, wenn die laufende LilyPond-Version älter als 2.27 ist.
% Alles andere ist für die aktuelle 2.27 geschrieben.
% Ab 2.27 gibt es \stanza als Musikfunktion (StanzaEvent), in 2.26 nur die Kontext-Property stanza.
stanza =
#(define-music-function (text) (markup?)
(context-spec-music (make-property-set 'stanza text) 'Bottom))
% Ab 2.27 heißt ly:chord-name::after-line-breaking item::enforce-musical-break-visibility.
#(define item::enforce-musical-break-visibility ly:chord-name::after-line-breaking)
+157
View File
@@ -0,0 +1,157 @@
%% Mehrere Lieder in einer Liederdatei (z.B. zwei Lieder auf einer Seite)
%%
%% Das erste Lied ist das Hauptlied (HEADER), alle weiteren werden als
%% eingebettete Lieder angemeldet. Eingebettete Lieder bekommen eigene
%% Eintraege im Inhaltsverzeichnis, in den Kategorie-, Autoren- und
%% Kapitelverzeichnissen, im PDF-Inhaltsverzeichnis, in der toc.csv und
%% eine eigene Liednummer. Im Footer steht die Songinfo des letzten
%% eingebetteten Lieds (abschaltbar mit footersong = ##f im HEADER bzw. mit
%% footersong = \andereslied umstellbar), die Songinfo des Hauptlieds wird
%% mit \songinfo-of selbst platziert.
%%
%% Beispiel:
%%
%% \include "../../lilypond-song-includes/liedbausteine/lied_a.ly"
%% liedA = \captureSong
%% \include "../../lilypond-song-includes/liedbausteine/lied_b.ly"
%% liedB = \captureSong
%%
%% TEXT = \markuplist {
%% \songinfo-of \liedA
%% \vspace #2
%% \embedded-song-title \liedB
%% \vspace #.5
%% \score { \songMusic \liedB \layout { \LAYOUT } }
%% \with-song \liedB {
%% \chordlyrics \secondVerse
%% }
%% }
%%
%% \restoreSong \liedA
%% \embedSong \liedB
%% \include "public_includes/layout_bottom.ily"
%%
%% \captureSong sichert den Header (basicSongInfo) und die Variablen aus
%% captured-song-variable-names, damit das naechste \include sie nicht
%% ueberschreibt. Das Ergebnis ist ein normales Header-Modul, es kann also
%% auch in \header { \liedA } verwendet werden.
%%
%% \restoreSong setzt basicSongInfo, HEADER und diese Variablen zurueck,
%% TEXT aber nicht. TEXT also vor \restoreSong schreiben, solange darin noch
%% Variablen des zuletzt eingebundenen Lieds (\secondVerse usw.) stehen.
%%
%% Ohne \with-song beziehen sich Strophen mit Akkorden auf das Hauptlied.
%%
%% Alle Verzeichniseintraege zeigen auf die erste Seite des Hauptlieds.
#(define captured-song-variable-names '(MUSIC global firstVoice verseChords))
#(define (song-variable song name)
(assq-ref (module-ref song 'captured-song-variables '()) name))
#(define (extract-and-check-vars-from-module headermodule varlist)
(let* ((headervars (if (module? headermodule) (hash-map->list cons (struct-ref headermodule 0)) '()))
(extract-var-and-check (lambda (headervar)
(let* ((variableref (assoc-ref headervars headervar))
(extracted (if variableref (variable-ref variableref) #f)))
(if (and extracted (not (and (string? extracted) (string-null? extracted)))) extracted #f)))))
(map (lambda (varsymbol)
(cons varsymbol (extract-var-and-check varsymbol))
) varlist)))
% Text des PDF-Lesezeichens eines Lieds, #f wenn es keins bekommt.
% Gleiche Regel wie in build-full-title.
#(define (song-pdf-bookmark-title headermodule)
(let* ((vars (extract-and-check-vars-from-module headermodule '(title starttext)))
(title (assq-ref vars 'title))
(starttext (assq-ref vars 'starttext))
(as-string (lambda (m) (if (markup? m) (markup->string m) ""))))
(cond ((and title starttext) (string-append (as-string starttext) " | " (as-string title)))
(title (as-string title))
(starttext (string-append (as-string starttext) " | "))
(else #f))))
#(define (embedded-song-list songs)
(if (list? songs) (filter module? songs) '()))
#(define (embedded-songs-of-header headermodule)
(if (module? headermodule)
(embedded-song-list (module-ref headermodule 'embeddedsongs '()))
'()))
% Ersetzt alle header:*-Properties durch die Felder aus headermodule, damit
% keine Felder des Hauptlieds in das andere Lied durchsickern.
#(define (header-module->props headermodule props)
(let ((header-key? (lambda (entry)
(and (pair? entry) (symbol? (car entry))
(string-prefix? "header:" (symbol->string (car entry)))))))
(cons
(map (lambda (kv) (cons (symbol-append 'header: (car kv)) (cdr kv)))
(ly:module->alist headermodule))
(map (lambda (alist) (remove header-key? alist)) props))))
captureSong =
#(define-scheme-function () ()
(let ((song (make-module)))
(ly:module-copy song basicSongInfo)
(module-define! song 'captured-song-variables
(filter-map
(lambda (name)
(let ((value (ly:parser-lookup name)))
(and (not (null? value)) (cons name value))))
captured-song-variable-names))
song))
% Stellt basicSongInfo, HEADER und die gesicherten Variablen eines Lieds
% wieder her, als waere es das zuletzt eingebundene.
restoreSong =
#(define-void-function (song) (module?)
(for-each
(lambda (kv) (ly:parser-define! (car kv) (cdr kv)))
(module-ref song 'captured-song-variables '()))
(ly:parser-define! 'basicSongInfo song)
(ly:parser-define! 'HEADER #{ \bookpart { \header { $song } } #}))
embedSong =
#(define-void-function (song) (module?)
(set! EMBEDDED_SONGS (append EMBEDDED_SONGS (list song))))
songMusic =
#(define-music-function (song) (module?)
(let ((music (song-variable song 'MUSIC)))
(if (ly:music? music) music (make-music 'SequentialMusic))))
% Strophen, \fromproperty #'header:... usw. beziehen sich innerhalb auf das
% angegebene Lied. Die Transposition des Hauptlieds wird nicht uebernommen.
#(define-markup-list-command (with-song layout props song args) (module? markup-list?)
(let* ((music-or-empty (lambda (name)
(let ((music (song-variable song name)))
(if (ly:music? music) music (make-music 'SequentialMusic)))))
(verseChords (music-or-empty 'verseChords))
(global (music-or-empty 'global))
(firstVoice (music-or-empty 'firstVoice)))
(interpret-markup-list layout
(prepend-alist-chain 'verse-chords #{ \chords { $verseChords } #}
(prepend-alist-chain 'verse-reference-voice #{ $global $firstVoice #}
(prepend-alist-chain 'transposition (cons #f #f)
(header-module->props song props))))
args)))
#(define-markup-command (songinfo-of layout props song) (module?)
(interpret-markup layout (header-module->props song props)
(make-fractional-line-width-markup (make-songinfo-content-markup))))
#(define-markup-command (embedded-song-title layout props song) (module?)
(interpret-markup layout (header-module->props song props)
(make-fill-line-markup (list (ly:output-def-lookup layout 'defaultTitleMarkup)))))
% PDF-Lesezeichen der eingebetteten Lieder, wird von build-full-title direkt
% nach dem Lesezeichen des Hauptlieds ausgegeben.
#(define-markup-command (embedded-songs-to-pdf-toc layout props) ()
(apply ly:stencil-add
(cons empty-stencil
(filter-map
(lambda (song)
(let ((bookmark (song-pdf-bookmark-title song)))
(and bookmark (make-pdf-toc-stencil bookmark 0))))
(embedded-song-list (chain-assoc-get 'header:embeddedsongs props '()))))))
+19 -4
View File
@@ -33,7 +33,8 @@
#(define (join-present items joiner) #(define (join-present items joiner)
(string-join (filter (lambda (item) (and (string? item) (not (string-null? (string-trim-both item))))) items) joiner)) (string-join (filter (lambda (item) (and (string? item) (not (string-null? (string-trim-both item))))) items) joiner))
#(define-markup-command (print-songinfo layout props) () % Songinfo aus den header:*-Properties, ohne Pruefung auf die letzte Seite.
#(define-markup-command (songinfo-content layout props) ()
(define (songinfo-from songId key) (define (songinfo-from songId key)
(let ((song (if (defined? 'SONG_DATA) (assoc-ref SONG_DATA songId) #f))) (let ((song (if (defined? 'SONG_DATA) (assoc-ref SONG_DATA songId) #f)))
(if song (if song
@@ -131,7 +132,6 @@
) )
(interpret-markup layout props (interpret-markup layout props
(if (chain-assoc-get 'page:is-bookpart-last-page props #f)
(let* ((authors (chain-assoc-get 'header:authors props #f)) (let* ((authors (chain-assoc-get 'header:authors props #f))
(poet-and-composers (poet-and-composer-from-authors authors)) (poet-and-composers (poet-and-composer-from-authors authors))
(songId (chain-assoc-get 'header:songId props #f)) (songId (chain-assoc-get 'header:songId props #f))
@@ -167,10 +167,25 @@
#:fontsize songInfoFontSize #:fontsize songInfoFontSize
#:sans #:sans
(ly:output-def-lookup layout 'songinfoMarkup) (ly:output-def-lookup layout 'songinfoMarkup)
))) ))))
(make-null-markup)))
) )
% Songinfo im Footer, nur auf der letzten Seite des Bookparts. Gibt es
% eingebettete Lieder (siehe embedded_songs.ily), steht hier die Songinfo des
% letzten eingebetteten Lieds, ausser im Header ist footersong gesetzt
% (##f = Hauptlied, oder ein mit \captureSong gesichertes Lied).
#(define-markup-command (print-songinfo layout props) ()
(if (chain-assoc-get 'page:is-bookpart-last-page props #f)
(let* ((footersong (chain-assoc-get 'header:footersong props 'auto))
(embedded (embedded-song-list (chain-assoc-get 'header:embeddedsongs props '())))
(song (if (eq? footersong 'auto)
(and (pair? embedded) (last embedded))
footersong)))
(interpret-markup layout
(if (module? song) (header-module->props song props) props)
(make-songinfo-content-markup)))
(interpret-markup layout props (make-null-markup))))
#(define-markup-command (print-pagenumber layout props)() #(define-markup-command (print-pagenumber layout props)()
(let ((label (chain-assoc-get 'header:myindexlabel props #f))) (let ((label (chain-assoc-get 'header:myindexlabel props #f)))
(interpret-markup layout props (interpret-markup layout props
@@ -65,10 +65,11 @@
;; Ist dieses Lied das erste eines Kapitels, wird der Kapitel-Eintrag ;; Ist dieses Lied das erste eines Kapitels, wird der Kapitel-Eintrag
;; unmittelbar vor dem Lied-Eintrag ausgegeben, damit die pdfmark-/OUT- ;; unmittelbar vor dem Lied-Eintrag ausgegeben, damit die pdfmark-/OUT-
;; Reihenfolge (Elternteil vor Kindern) im PDF-Stream garantiert stimmt. ;; Reihenfolge (Elternteil vor Kindern) im PDF-Stream garantiert stimmt.
;; Eingebettete Lieder (embedded_songs.ily) folgen direkt nach dem Lied.
(if (and (string? chaptertitle) (not (string-null? chaptertitle))) (if (and (string? chaptertitle) (not (string-null? chaptertitle)))
(markup #:chapter-to-pdf-toc chaptercount chaptertitle (markup #:chapter-to-pdf-toc chaptercount chaptertitle
#:title-to-pdf-toc pdfbookmark title-markup) #:title-to-pdf-toc pdfbookmark #:embedded-songs-to-pdf-toc title-markup)
(markup #:title-to-pdf-toc pdfbookmark title-markup)) (markup #:title-to-pdf-toc pdfbookmark #:embedded-songs-to-pdf-toc title-markup))
make-null-markup) make-null-markup)
))) )))
+43 -1
View File
@@ -89,7 +89,7 @@
(if (and (string? arg) (string-null? arg)) (if (and (string? arg) (string-null? arg))
" " " "
#{\markup #{\markup
\score { \new Lyrics { \lyricmode { #(if (ly:music? arg) arg #{ \set stanza = #arg #}) "" } } \layout { $verselayout #(customized-layout verselayout) } } \score { \new Lyrics { \lyricmode { #(if (ly:music? arg) arg #{ \stanza #arg #}) "" } } \layout { $verselayout #(customized-layout verselayout) } }
#} #}
))) )))
@@ -249,6 +249,13 @@ Chord_lyrics_spacing_engraver =
(lyrics-seen-since-break #f) (lyrics-seen-since-break #f)
(have-a-rest #f) (have-a-rest #f)
(stanza #f) (stanza #f)
; Akkord auf einer Skip-Silbe "_" nach Wortende soll über die folgende Silbe
(word-continues #f)
(continuation-this-step #f)
(lyric-skip-this-step #f)
(syllable-this-step #f)
(chord-this-step #f)
(chord-on-lyric-skip #f)
(place-at-right-edge (place-at-right-edge
(lambda (grob anchor padding) (lambda (grob anchor padding)
(let ((anchor-width (interval-length (ly:grob-extent anchor anchor X)))) (let ((anchor-width (interval-length (ly:grob-extent anchor anchor X))))
@@ -256,6 +263,25 @@ Chord_lyrics_spacing_engraver =
(ly:grob-set-property! grob 'X-offset (+ padding anchor-width)) (ly:grob-set-property! grob 'X-offset (+ padding anchor-width))
)))) ))))
(make-engraver (make-engraver
((start-translation-timestep engraver)
(set! continuation-this-step #f)
(set! lyric-skip-this-step #f)
(set! syllable-this-step #f)
(set! chord-this-step #f)
)
((stop-translation-timestep engraver)
(if (and chord-on-lyric-skip (not (eq? chord-on-lyric-skip chord-this-step)))
(begin
(if (and syllable-this-step (not chord-this-step))
(begin
(ly:grob-set-parent! chord-on-lyric-skip X syllable-this-step)
(ly:grob-set-property! chord-on-lyric-skip 'X-offset 0)
(ly:grob-set-property! chord-on-lyric-skip 'extra-spacing-width '(+inf.0 . -inf.0))
(set! lyric-width-since-last-chord (interval-length (ly:grob-extent syllable-this-step syllable-this-step X)))))
(set! chord-on-lyric-skip #f)))
(if syllable-this-step
(set! word-continues continuation-this-step))
)
(listeners (listeners
((multi-measure-rest-event engraver event) ((multi-measure-rest-event engraver event)
(set! have-a-rest #t) (set! have-a-rest #t)
@@ -263,8 +289,19 @@ Chord_lyrics_spacing_engraver =
((rest-event engraver event) ((rest-event engraver event)
(set! have-a-rest #t) (set! have-a-rest #t)
) )
((hyphen-event engraver event)
(set! continuation-this-step #t)
)
((extender-event engraver event)
(set! continuation-this-step #t)
)
((lyric-event engraver event) ((lyric-event engraver event)
(set! have-a-rest #f) (set! have-a-rest #f)
; "_" wird zu " " und erzeugt keine LyricText
(if (equal? (ly:event-property event 'text) " ")
(begin
(set! lyric-skip-this-step #t)
(set! chord-on-lyric-skip #f)))
(set! music-columns-for-last-syllable 0) (set! music-columns-for-last-syllable 0)
) )
((break-event engraver event) ((break-event engraver event)
@@ -274,6 +311,7 @@ Chord_lyrics_spacing_engraver =
(set! last-printed-chord #f) (set! last-printed-chord #f)
(set! chord-width-since-last-lyric 0) (set! chord-width-since-last-lyric 0)
(set! lyrics-seen-since-break #f) (set! lyrics-seen-since-break #f)
(set! chord-on-lyric-skip #f)
)) ))
(acknowledgers (acknowledgers
((musical-paper-column-interface this-engraver grob source-engraver) ((musical-paper-column-interface this-engraver grob source-engraver)
@@ -294,6 +332,7 @@ Chord_lyrics_spacing_engraver =
(ly:grob-set-property! grob 'X-offset gap-for-starting-rest) (ly:grob-set-property! grob 'X-offset gap-for-starting-rest)
))) )))
) )
(set! syllable-this-step grob)
(set! last-lyric-syllable grob) (set! last-lyric-syllable grob)
(set! chord-width-since-last-lyric 0) (set! chord-width-since-last-lyric 0)
(set! lyrics-seen-since-break #t) (set! lyrics-seen-since-break #t)
@@ -321,6 +360,9 @@ Chord_lyrics_spacing_engraver =
(cons (car (ly:grob-property last-lyric-syllable 'extra-spacing-width '(0 . 0))) (+ chord-gap chord-overwidth))) (cons (car (ly:grob-property last-lyric-syllable 'extra-spacing-width '(0 . 0))) (+ chord-gap chord-overwidth)))
) )
(set! lyric-width-since-last-chord (* (if last-lyric-syllable (interval-length (ly:grob-extent last-lyric-syllable last-lyric-syllable X)) 0) (- 1 (/ 1.0 music-columns-for-last-syllable)))) (set! lyric-width-since-last-chord (* (if last-lyric-syllable (interval-length (ly:grob-extent last-lyric-syllable last-lyric-syllable X)) 0) (- 1 (/ 1.0 music-columns-for-last-syllable))))
(set! chord-this-step grob)
(if (and lyric-skip-this-step (not have-a-rest) (not word-continues))
(set! chord-on-lyric-skip grob))
(set! last-printed-chord grob) (set! last-printed-chord grob)
(set! last-lyric-syllable #f) (set! last-lyric-syllable #f)
) )
+25 -10
View File
@@ -187,20 +187,24 @@ songs =
;; Lieder duerfen als Kapitel-Kinder gezaehlt werden -- sonst wird die ;; Lieder duerfen als Kapitel-Kinder gezaehlt werden -- sonst wird die
;; /Count zu hoch und der erste Folgeeintrag (z.B. ein spaeteres Kapitel) ;; /Count zu hoch und der erste Folgeeintrag (z.B. ein spaeteres Kapitel)
;; rutscht faelschlich als Kind mit hinein. ;; rutscht faelschlich als Kind mit hinein.
(emits-bookmark? ;; Eingebettete Lieder (embedded_songs.ily) haben jeweils ein eigenes
;; Lesezeichen, daher die Anzahl statt eines Booleans.
(bookmark-count
(lambda (e) (lambda (e)
(and (not (eq? (car e) 'emptyPage)) (if (or (eq? (car e) 'emptyPage)
(not (eq? (car e) 'imagePage)) (eq? (car e) 'imagePage)
(not (eq? (car e) 'markupPage)) (eq? (car e) 'markupPage))
(let ((vars (extract-and-check-vars-from-header 0
(assq-ref (cdr e) 'header) '(title starttext)))) (let* ((headermodule (ly:book-header (assq-ref (cdr e) 'header)))
(if (or (assq-ref vars 'title) (assq-ref vars 'starttext)) #t #f))))) (vars (extract-and-check-vars-from-module headermodule '(title starttext))))
(+ (if (or (assq-ref vars 'title) (assq-ref vars 'starttext)) 1 0)
(count song-pdf-bookmark-title (embedded-songs-of-header headermodule)))))))
(count-bookmark-songs (count-bookmark-songs
(lambda (start end) (lambda (start end)
(let loop ((i 0) (l ordered) (c 0)) (let loop ((i 0) (l ordered) (c 0))
(cond ((or (null? l) (>= i end)) c) (cond ((or (null? l) (>= i end)) c)
((< i start) (loop (+ i 1) (cdr l) c)) ((< i start) (loop (+ i 1) (cdr l) c))
(else (loop (+ i 1) (cdr l) (if (emits-bookmark? (car l)) (+ c 1) c))))))) (else (loop (+ i 1) (cdr l) (+ c (bookmark-count (car l)))))))))
;; chapters: (title . start-index). start-index = Anzahl der song-list- ;; chapters: (title . start-index). start-index = Anzahl der song-list-
;; Eintraege beim Aufruf von songsChapter = Index in `ordered`, ab dem ;; Eintraege beim Aufruf von songsChapter = Index in `ordered`, ab dem
;; das Kapitel beginnt. ;; das Kapitel beginnt.
@@ -300,12 +304,23 @@ songs =
(music (assq-ref songvars 'music)) (music (assq-ref songvars 'music))
(layout (assq-ref songvars 'layout)) (layout (assq-ref songvars 'layout))
(text-pages (assq-ref songvars 'text-pages)) (text-pages (assq-ref songvars 'text-pages))
(label (assq-ref songvars 'label))) (label (assq-ref songvars 'label))
(toc-music (header-module-to-toc! (ly:book-header header) label)))
(set! song-number newnumber) (set! song-number newnumber)
;; Eingebettete Lieder: eigene Liednummer, gleiche Seite (label)
(for-each
(lambda (song)
(let ((tocheader (make-module)))
(set! song-number (+ 1 song-number))
(ly:module-copy tocheader song)
(module-define! tocheader 'songnumber (number->string song-number))
(module-define! tocheader 'chapterref current-chapter)
(header-module-to-toc! tocheader label)))
(embedded-songs-of-header (ly:book-header header)))
#{ #{
\bookpart { \bookpart {
$header $header
\headerToTOC #header #label $toc-music
\score { $music \layout { $layout songfilename = $(symbol->string filename) } } \score { $music \layout { $layout songfilename = $(symbol->string filename) } }
$(add-text-pages text-pages) $(add-text-pages text-pages)
} }
+17 -15
View File
@@ -304,16 +304,9 @@ indexSection =
(add-index-item! 'indexSectionMarkup (prepend-alist-chain 'combine-with-next #t (prepend-alist-chain 'rawtext text '())) sorttext)) (add-index-item! 'indexSectionMarkup (prepend-alist-chain 'combine-with-next #t (prepend-alist-chain 'rawtext text '())) sorttext))
#(define (extract-and-check-vars-from-header bookheader varlist) #(define (extract-and-check-vars-from-header bookheader varlist)
(let* ((headervars (hash-map->list cons (struct-ref (ly:book-header bookheader) 0))) (extract-and-check-vars-from-module (ly:book-header bookheader) varlist))
(extract-var-and-check (lambda (headervar)
(let* ((variableref (assoc-ref headervars headervar))
(extracted (if variableref (variable-ref variableref) #f)))
(if (and extracted (not (and (string? extracted) (string-null? extracted)))) extracted #f)))))
(map (lambda (varsymbol)
(cons varsymbol (extract-var-and-check varsymbol))
) varlist)))
headerToTOC = #(define-music-function (parser location header label) (ly:book? symbol?) #(define (header-module-to-toc! headermodule label)
(define (all-author-ids authors) (define (all-author-ids authors)
(let ((poetIds (find-author-ids-by 'text authors)) (let ((poetIds (find-author-ids-by 'text authors))
(translatorIds (find-author-ids-by 'translation authors)) (translatorIds (find-author-ids-by 'translation authors))
@@ -331,7 +324,7 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
)) ))
(let* (let*
( (
(extractedheadervars (extract-and-check-vars-from-header header '(title starttext alttitle categorytitle categories authors songnumber chapterref))) (extractedheadervars (extract-and-check-vars-from-module headermodule '(title starttext alttitle categorytitle categories authors songnumber chapterref)))
(title (assq-ref extractedheadervars 'title)) (title (assq-ref extractedheadervars 'title))
(starttext (assq-ref extractedheadervars 'starttext)) (starttext (assq-ref extractedheadervars 'starttext))
(alttitle (assq-ref extractedheadervars 'alttitle)) (alttitle (assq-ref extractedheadervars 'alttitle))
@@ -357,6 +350,9 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(if title (add-to-toc! title #f) #{ #}) (if title (add-to-toc! title #f) #{ #})
)) ))
headerToTOC = #(define-music-function (parser location header label) (ly:book? symbol?)
(header-module-to-toc! (ly:book-header header) label))
%% https://github.com/NalaGinrut/guile-csv/blob/master/csv/csv.scm %% https://github.com/NalaGinrut/guile-csv/blob/master/csv/csv.scm
@@ -402,11 +398,8 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(para-lines (map cleanup-whitespaces para-strings))) (para-lines (map cleanup-whitespaces para-strings)))
(string-join para-lines "\n"))) (string-join para-lines "\n")))
(define (generate-toc-csv labelPageTable) (define (generate-toc-csv labelPageTable)
(let ((song-lines (map (lambda (song) (define (song-line filename page-number headermodule)
(let* ((filename (symbol->string (car song))) (let* ((extractedheadervars (extract-and-check-vars-from-module headermodule
(songvars (cdr song))
(page-number (assoc-get (assq-ref songvars 'label) labelPageTable))
(extractedheadervars (extract-and-check-vars-from-header (assq-ref songvars 'header)
'(title starttext alttitle categorytitle categories authors year_text year_melody year_translation year_composition year_adaption infotext translation pronunciation copyright source))) '(title starttext alttitle categorytitle categories authors year_text year_melody year_translation year_composition year_adaption infotext translation pronunciation copyright source)))
(headervar-or-empty (lambda (varsym) (headervar-or-empty (lambda (varsym)
(let ((extracted (assq-ref extractedheadervars varsym))) (let ((extracted (assq-ref extractedheadervars varsym)))
@@ -452,6 +445,15 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(format-info-paragraphs (headervar-or-empty 'translation)) (format-info-paragraphs (headervar-or-empty 'translation))
(format-info-paragraphs (headervar-or-empty 'pronunciation)) (format-info-paragraphs (headervar-or-empty 'pronunciation))
)))) ))))
;; Eingebettete Lieder (embedded_songs.ily) bekommen eigene Zeilen mit
;; Dateiname und Seite des Hauptlieds.
(let ((song-lines (append-map (lambda (song)
(let* ((filename (symbol->string (car song)))
(songvars (cdr song))
(page-number (assoc-get (assq-ref songvars 'label) labelPageTable))
(headermodule (ly:book-header (assq-ref songvars 'header))))
(map (lambda (module) (song-line filename page-number module))
(cons headermodule (embedded-songs-of-header headermodule)))))
(alist-delete 'markupPage (alist-delete 'imagePage (alist-delete 'emptyPage song-list)))))) (alist-delete 'markupPage (alist-delete 'imagePage (alist-delete 'emptyPage song-list))))))
(call-with-output-file "toc.csv" (call-with-output-file "toc.csv"
(lambda (port) (lambda (port)
+22 -12
View File
@@ -1,10 +1,17 @@
MUSIC = { \transposable #TRANSPOSITION \MUSIC } MUSIC = { \transposable #TRANSPOSITION \MUSIC }
%% Eingebettete Lieder (siehe embedded_songs.ily) im Header des Hauptlieds
%% vermerken, daraus bauen Footer, PDF-Lesezeichen und Verzeichnisse.
#(if (and (pair? EMBEDDED_SONGS) (ly:book? HEADER) (ly:book-header HEADER))
(module-define! (ly:book-header HEADER) 'embeddedsongs EMBEDDED_SONGS))
LAYOUT = \layout { LAYOUT = \layout {
\LAYOUT \LAYOUT
#(customized-layout LAYOUT) #(customized-layout LAYOUT)
} }
#(customize-embedded-score-layouts! (list TEXT TEXT_PAGES))
verselayout = \layout { verselayout = \layout {
\LAYOUT \LAYOUT
\context { \context {
@@ -79,18 +86,21 @@ TEXT = \markuplist {
(let ((output-name (ly:parser-output-name))) (let ((output-name (ly:parser-output-name)))
(set! chordpro-current-filename output-name)) (set! chordpro-current-filename output-name))
;; Try to extract metadata from basicSongInfo \header block ;; Try to extract metadata from the HEADER (falls back to basicSongInfo)
(when (defined? 'basicSongInfo) (let ((header-module (if (and (ly:book? HEADER) (ly:book-header HEADER))
(let* ((header-alist (ly:module->alist basicSongInfo)) (ly:book-header HEADER)
(title (assoc-ref header-alist 'title)) (and (defined? 'basicSongInfo) basicSongInfo))))
(authors (assoc-ref header-alist 'authors))) (when header-module
(when title (let* ((header-alist (ly:module->alist header-module))
(set! chordpro-header-title (title (assoc-ref header-alist 'title))
(if (markup? title) (authors (assoc-ref header-alist 'authors)))
(markup->string title) (when title
(if (string? title) title "Untitled")))) (set! chordpro-header-title
(when authors (if (markup? title)
(set! chordpro-header-authors authors))))) (markup->string title)
(if (string? title) title "Untitled"))))
(when authors
(set! chordpro-header-authors authors))))))
(add-score #{ (add-score #{
\score { \score {