diff --git a/private_includes/base/all.ily b/private_includes/base/all.ily index 49cdc4e..ca6a665 100644 --- a/private_includes/base/all.ily +++ b/private_includes/base/all.ily @@ -36,6 +36,7 @@ \include "merge_rests_engraver_override.ily" \include "basic_format_and_style_settings.ily" \include "eps_file_from_song_dir.ily" +\include "embedded_songs.ily" \include "title_with_category_images.ily" \include "chord_settings.ily" \include "chordpro.ily" @@ -54,6 +55,7 @@ HEADER = {} MUSIC = {} TEXT = \markuplist {} TEXT_PAGES = #f +EMBEDDED_SONGS = #'() verseChords = {} firstVoice = {} diff --git a/private_includes/base/embedded_songs.ily b/private_includes/base/embedded_songs.ily new file mode 100644 index 0000000..4b9c5de --- /dev/null +++ b/private_includes/base/embedded_songs.ily @@ -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 '())))))) diff --git a/private_includes/base/footer_with_songinfo.ily b/private_includes/base/footer_with_songinfo.ily index 8c3f31b..196cc51 100644 --- a/private_includes/base/footer_with_songinfo.ily +++ b/private_includes/base/footer_with_songinfo.ily @@ -33,7 +33,8 @@ #(define (join-present 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) (let ((song (if (defined? 'SONG_DATA) (assoc-ref SONG_DATA songId) #f))) (if song @@ -131,7 +132,6 @@ ) (interpret-markup layout props - (if (chain-assoc-get 'page:is-bookpart-last-page props #f) (let* ((authors (chain-assoc-get 'header:authors props #f)) (poet-and-composers (poet-and-composer-from-authors authors)) (songId (chain-assoc-get 'header:songId props #f)) @@ -167,10 +167,25 @@ #:fontsize songInfoFontSize #:sans (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)() (let ((label (chain-assoc-get 'header:myindexlabel props #f))) (interpret-markup layout props diff --git a/private_includes/base/title_with_category_images.ily b/private_includes/base/title_with_category_images.ily index 0c3b649..1b33855 100644 --- a/private_includes/base/title_with_category_images.ily +++ b/private_includes/base/title_with_category_images.ily @@ -65,10 +65,11 @@ ;; Ist dieses Lied das erste eines Kapitels, wird der Kapitel-Eintrag ;; unmittelbar vor dem Lied-Eintrag ausgegeben, damit die pdfmark-/OUT- ;; 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))) (markup #:chapter-to-pdf-toc chaptercount chaptertitle - #:title-to-pdf-toc pdfbookmark title-markup) - (markup #: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 #:embedded-songs-to-pdf-toc title-markup)) make-null-markup) ))) diff --git a/private_includes/book/book_include.ily b/private_includes/book/book_include.ily index 43f80e2..e1698d2 100644 --- a/private_includes/book/book_include.ily +++ b/private_includes/book/book_include.ily @@ -187,20 +187,24 @@ songs = ;; Lieder duerfen als Kapitel-Kinder gezaehlt werden -- sonst wird die ;; /Count zu hoch und der erste Folgeeintrag (z.B. ein spaeteres Kapitel) ;; 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) - (and (not (eq? (car e) 'emptyPage)) - (not (eq? (car e) 'imagePage)) - (not (eq? (car e) 'markupPage)) - (let ((vars (extract-and-check-vars-from-header - (assq-ref (cdr e) 'header) '(title starttext)))) - (if (or (assq-ref vars 'title) (assq-ref vars 'starttext)) #t #f))))) + (if (or (eq? (car e) 'emptyPage) + (eq? (car e) 'imagePage) + (eq? (car e) 'markupPage)) + 0 + (let* ((headermodule (ly:book-header (assq-ref (cdr e) 'header))) + (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 (lambda (start end) (let loop ((i 0) (l ordered) (c 0)) (cond ((or (null? l) (>= i end)) 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- ;; Eintraege beim Aufruf von songsChapter = Index in `ordered`, ab dem ;; das Kapitel beginnt. @@ -300,12 +304,23 @@ songs = (music (assq-ref songvars 'music)) (layout (assq-ref songvars 'layout)) (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) + ;; 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 { $header - \headerToTOC #header #label + $toc-music \score { $music \layout { $layout songfilename = $(symbol->string filename) } } $(add-text-pages text-pages) } diff --git a/private_includes/book/toc_include.ily b/private_includes/book/toc_include.ily index a79ae29..1c96cce 100644 --- a/private_includes/book/toc_include.ily +++ b/private_includes/book/toc_include.ily @@ -304,16 +304,9 @@ indexSection = (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) - (let* ((headervars (hash-map->list cons (struct-ref (ly:book-header bookheader) 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))) + (extract-and-check-vars-from-module (ly:book-header bookheader) varlist)) -headerToTOC = #(define-music-function (parser location header label) (ly:book? symbol?) +#(define (header-module-to-toc! headermodule label) (define (all-author-ids authors) (let ((poetIds (find-author-ids-by 'text authors)) (translatorIds (find-author-ids-by 'translation authors)) @@ -331,7 +324,7 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s )) (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)) (starttext (assq-ref extractedheadervars 'starttext)) (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) #{ #}) )) +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 @@ -402,11 +398,8 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s (para-lines (map cleanup-whitespaces para-strings))) (string-join para-lines "\n"))) (define (generate-toc-csv labelPageTable) - (let ((song-lines (map (lambda (song) - (let* ((filename (symbol->string (car song))) - (songvars (cdr song)) - (page-number (assoc-get (assq-ref songvars 'label) labelPageTable)) - (extractedheadervars (extract-and-check-vars-from-header (assq-ref songvars 'header) + (define (song-line filename page-number headermodule) + (let* ((extractedheadervars (extract-and-check-vars-from-module headermodule '(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) (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 '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)))))) (call-with-output-file "toc.csv" (lambda (port) diff --git a/public_includes/layout_bottom.ily b/public_includes/layout_bottom.ily index 3a4f03a..60a15b7 100644 --- a/public_includes/layout_bottom.ily +++ b/public_includes/layout_bottom.ily @@ -1,5 +1,10 @@ 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 #(customized-layout LAYOUT) @@ -79,18 +84,21 @@ TEXT = \markuplist { (let ((output-name (ly:parser-output-name))) (set! chordpro-current-filename output-name)) - ;; Try to extract metadata from basicSongInfo \header block - (when (defined? 'basicSongInfo) - (let* ((header-alist (ly:module->alist basicSongInfo)) - (title (assoc-ref header-alist 'title)) - (authors (assoc-ref header-alist 'authors))) - (when title - (set! chordpro-header-title - (if (markup? title) - (markup->string title) - (if (string? title) title "Untitled")))) - (when authors - (set! chordpro-header-authors authors))))) + ;; Try to extract metadata from the HEADER (falls back to basicSongInfo) + (let ((header-module (if (and (ly:book? HEADER) (ly:book-header HEADER)) + (ly:book-header HEADER) + (and (defined? 'basicSongInfo) basicSongInfo)))) + (when header-module + (let* ((header-alist (ly:module->alist header-module)) + (title (assoc-ref header-alist 'title)) + (authors (assoc-ref header-alist 'authors))) + (when title + (set! chordpro-header-title + (if (markup? title) + (markup->string title) + (if (string? title) title "Untitled")))) + (when authors + (set! chordpro-header-authors authors)))))) (add-score #{ \score {