Author SHA1 Message Date
tux 286779fdac extract meta data from lilypond compilation
* write that as yaml file next to the other outputs
* extract lyrics
* makes chordpro export work in songbooks
* more metadata for chordpro export
2026-07-12 23:11:36 +02:00
tux 7de31cf0dd chapter toc 2026-07-07 12:16:13 +02:00
tux ae72640b1a chapters for every pagetype 2026-07-07 11:05:47 +02:00
tux 7076b16b63 Use chapters for digital toc
* introduce \songsChapter and \markuppage
* have nested chapters in PDF toc
2026-07-05 14:09:44 +02:00
tux 50e08220c4 make customEps path resolution more robust 2026-07-01 17:58:10 +02:00
tux 40f483bc67 default to numeric time signatures 2026-06-21 16:44:49 +02:00
tux 9b1297f675 fix tag group usage in markups 2026-06-04 00:05:42 +02:00
zuk aed11a70a7 Add meloref to author system 2026-05-12 10:42:14 +02:00
zuk 92a129f346 Fix double melody year in songinfo 2026-05-03 22:22:59 +02:00
zuk bb940b2a3b Fix transposeGlobal: Make additional transpositions possible 2026-04-22 11:45:57 +02:00
tux 997f757cd6 Chordpro export 2026-04-18 16:47:22 +02:00
tux 6e528a5328 fix markup tag group trouble accross songs 2026-04-10 14:29:20 +02:00
zuk 71c8c0385c switch swingStyle to textMark 2026-03-31 18:55:12 +02:00
tux f075946777 use right translationAuthorPrefix in authorContributionFormat 2026-03-12 22:25:50 +01:00
tux a0de02126c no "add" as additionalPitchPrefix 2026-03-10 19:17:40 +01:00
tux 7ece3b9c10 underline for LyricText 2026-03-09 17:01:21 +01:00
tux 7fc99e8883 use new chords and page references of LilyPond 2.25.35 2026-03-08 13:37:31 +01:00
tux b3feb3aa6d padding for stop tags and refactoring 2026-02-22 20:33:18 +01:00
tux 7b166f6fc3 pad left for stanzas to have a minimum distance to SystemStartBar 2026-02-22 13:22:45 +01:00
tux 9c437d0f06 reset tag groups for each song 2026-02-10 17:18:52 +01:00
tux a52f993678 more flexible titeling 2026-02-01 01:45:10 +01:00
tux bbd6d44455 Autorensystem flexibilisiert 2026-01-11 20:03:31 +01:00
zuk f68e2f10ae implement default bridge style 2025-12-29 18:06:00 +01:00
tux 463d61fbd9 fix customize layout sizing 2025-12-22 11:29:32 +01:00
tux 2d14a5b632 new engraver for spacing in chordlyrics 2025-12-15 21:41:45 +01:00
tux 368a1b96aa rework stanza handler 2025-12-15 11:49:30 +01:00
tux f57b1c4ec3 customize text chord distance in old system, too 2025-12-15 01:47:05 +01:00
tux e530bdf090 make verselayout really work 2025-12-15 01:26:19 +01:00
zukandtux c97c856b05 Use \tempo command for midi speed
* Remove Metronome mark engraver
* Remove midiQuarterNoteSpeed
2025-12-13 12:50:01 +01:00
tuxandtux 76b81a9968 no breaks in toc after section title 2025-11-24 20:45:00 +01:00
tux 3ff5a36106 better load song include data in windows 2025-11-24 16:47:13 +01:00
tux 7c92c65c82 bessere Transliteration für europäische Sonderzeichen 2025-11-01 10:29:29 +01:00
tux b2f1a7dc86 override-stanza with multiple numbers 2025-10-31 15:15:21 +01:00
tux 893010c4a9 fix paragraph splitting regex for windows 2025-10-30 12:31:28 +01:00
zuk 8aa0e3fdc3 Fix black Ties and Slurs for secondVoiceStyle 2025-10-29 13:14:52 +01:00
tux 5364f93db4 fix pdf page numbering 2025-10-26 22:16:25 +01:00
zukandtux aa0e3816d0 Fix black rests in secondVoiceStyle 2025-10-26 18:03:55 +01:00
tux 3b0c320839 better chord lyric distance handling 2025-09-21 02:00:08 +02:00
tux 12abf0a5f7 native scheme yaml parser 2025-09-20 13:40:15 +02:00
tux a01e95f31d fix categories and authors index 2025-09-19 18:47:26 +02:00
tux 90feb6dfbb separate translation prefixes for authors and songinfo 2025-09-19 17:19:37 +02:00
tux ede5568962 be able to format everything of the songinfo with markup 2025-09-17 13:02:41 +02:00
tux 66b5c37755 adaption_text adaption_music 2025-08-31 15:53:52 +02:00
25 changed files with 3732 additions and 968 deletions
+1 -1
View File
@@ -1,8 +1,8 @@
# ---> Lilypond
*.pdf
*.cho
*.ps
*.midi
*.mid
*.log
*~
+19 -4
View File
@@ -19,17 +19,30 @@
(string-append (dirname (current-filename)) file-name-separator-string))
"scm" file-name-separator-string filename
)))))
(scm-load "json_parser.scm")
(scm-load "resolve_inherits.scm")
(scm-load "yaml_parser.scm")))
#(define AUTHOR_DATA (if (defined? 'AUTHOR_DATA) AUTHOR_DATA (parse-yml-file "../../lilypond-song-includes/data/authors.yml")))
#(define SONG_DATA (if (defined? 'SONG_DATA) SONG_DATA (parse-yml-file "../../lilypond-song-includes/data/songs.yml")))
(scm-load "yaml_parser.scm")
(scm-load "yaml_writer.scm")))
#(define (song-includes-data-path filename)
(string-join
(list
(dirname (dirname (dirname (dirname (current-filename)))))
"lilypond-song-includes"
"data"
filename)
file-name-separator-string))
#(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"))))
\include "merge_rests_engraver_override.ily"
\include "basic_format_and_style_settings.ily"
\include "eps_file_from_song_dir.ily"
\include "title_with_category_images.ily"
\include "chord_settings.ily"
\include "chordpro.ily"
\include "data_extractor.ily"
\include "transposition.ily"
\include "markup_tag_groups_hack.ily"
\include "verses_with_chords.ily"
\include "arrows_in_scores.ily"
\include "swing_style.ily"
@@ -47,3 +60,5 @@ TEXT_PAGES = #f
verseChords = {}
firstVoice = {}
global = {}
\resetTagGroups
@@ -29,6 +29,11 @@
(if (null? stanzanumbers)
refString
(ly:format refStringWithNumbers (string-join (map (lambda (stanzanumber) (ly:format "~a" stanzanumber)) stanzanumbers) ", ")))))
bridgeMarkupFormatter = #(lambda (layout props stanzanumbers)
(interpret-markup layout props
(if (null? stanzanumbers)
bridgeString
(ly:format bridgeStringWithNumbers (string-join (map (lambda (stanzanumber) (ly:format "~a" stanzanumber)) stanzanumbers) ", ")))))
}
generalLayout = \layout {
@@ -44,13 +49,15 @@ generalLayout = \layout {
\context {
\Staff
\accidentalStyle modern-voice-cautionary
\consists Merge_rests_engraver
\consists \Better_Merge_rests_engraver
}
\context {
\Score
\remove "Bar_number_engraver"
\remove "Metronome_mark_engraver"
\RemoveEmptyStaves
\override VerticalAxisGroup.remove-first = ##t
\numericTimeSignature
\overrideTimeSignatureSettings
4/4 % timeSignatureFraction
1/4 % baseMomentFraction
@@ -67,14 +74,22 @@ generalLayout = \layout {
% ich will lines breaken wie ich will!
\remove "Forbid_line_break_engraver"
\override NoteHead.layer = 2
\override Rest.layer = 2
\override Dots.layer = 2
\override Stem.layer = 2
\override Flag.layer = 2
\override Beam.layer = 2
\override Slur.layer = 2
\override Tie.layer = 2
\override Accidental.layer = 2
}
}
#(define (customized-layout base-layout)
(let
((custom-size (ly:output-def-lookup base-layout 'size #f)))
(if custom-size (layout-set-staff-size custom-size))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% kleine Helferlein:
@@ -83,25 +98,34 @@ textp = \lyricmode { \markup { \raise #1 \musicglyph #"rests.3" } }
% zweite Stimme alles grau
secondVoiceStyle = {
\override NoteHead.color = #grey
\override Rest.color = #grey
\override Dots.color = #grey
\override Stem.color = #grey
\override Flag.color = #grey
\override Beam.color = #grey
\override Slur.color = #grey
\override Tie.color = #grey
\override Accidental.color = #grey
\override NoteHead.layer = 1
\override Rest.layer = 1
\override Dots.layer = 1
\override Stem.layer = 1
\override Flag.layer = 1
\override Beam.layer = 1
\override Slur.layer = 1
\override Tie.layer = 1
\override Accidental.layer = 1
}
firstVoiceStyle = {
\override NoteHead.color = #black
\override Rest.color = #black
\override Dots.color = #black
\override Stem.color = #black
\override Flag.color = #black
\override Beam.color = #black
\override Slur.color = #black
\override Tie.color = #black
\override Accidental.color = #black
}
@@ -115,45 +139,84 @@ romanStanza =
#{ \override StanzaNumber.style = #'roman #})
override-stanza =
#(define-music-function (parser location stanzanumber) (number?)
#(define-music-function (parser location stanzanumbers) (number-list?)
#{
\once \override StanzaNumber.forced-spacing = #stanzanumber % misuse property "forced-spacing" to override the stanzanumber
\once \override StanzaNumber.details.custom-stanzanumber-override = #stanzanumbers
#}
)
#(define (handle-stanza-numbers context numbers number-formater)
(let* ((stanzanumbers (ly:assoc-get 'custom-stanzanumber-override (ly:assoc-get 'details (ly:context-grob-definition context 'StanzaNumber) '()) numbers))
(stanza-style (ly:assoc-get 'style (ly:context-grob-definition context 'StanzaNumber)))
(roman-format (lambda (stanzanumber) (format #f "~@r" stanzanumber))))
(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)
#{
\once \override StanzaNumber.layer = 23 % 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-numbers = #stanzanumbers
\applyContext
#(lambda (context)
(let* ((stanzanumber-override (ly:assoc-get 'forced-spacing (ly:context-grob-definition context 'StanzaNumber) #f))
(stanza-style (ly:assoc-get 'style (ly:context-grob-definition context 'StanzaNumber)))
(stanza-format (lambda (stanzanumber) (format #f (if (eq? stanza-style 'roman) romanStanzaFormat stanzaFormat) stanzanumber))))
(ly:context-set-property! context 'stanza
(string-join (map stanza-format
(if stanzanumber-override (list stanzanumber-override) stanzanumbers))
", "))))
(handle-stanza-numbers context stanzanumbers
(lambda (numbers) (string-join (map (lambda (n) (format #f stanzaFormat n)) numbers) ", "))))
#}
)
ref =
#(define-music-function (stanzanumbers lyrics) ((number-list? (list)) ly:music?)
#{ \lyricmode {
\once \override StanzaNumber.layer = 23 % set this to signal that there is a real stanza and no repeat signs
\set stanza = #(make-on-the-fly-markup (lambda (layout props m) ((ly:output-def-lookup layout 'refMarkupFormatter) layout props stanzanumbers)) (make-null-markup))
\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 = #'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
}
#}
)
bridge =
#(define-music-function (stanzanumbers lyrics) ((number-list? (list)) ly:music?)
#{ \lyricmode {
\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 = #'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
}
#}
)
% 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.layer = 23 we set that also as stanza.
% if there was a stanza already set by the stanza function with StanzaNumber.details.custom-realstanza = ##t we set that also as stanza.
% Sets custom-inline-text for ChordPro export so it can collect the repeat sign separately
repStartWithTag = \lyricmode {
\tag #'repeats {
\once \override StanzaNumber.details.custom-inline-text = \repStart
\once \override StanzaNumber.details.custom-inline-direction = #LEFT
\applyContext
#(lambda (context)
(let ((lastStanza (ly:context-property context 'stanza))
(printLastStanza (= (ly:assoc-get 'layer (ly:context-grob-definition context 'StanzaNumber) 0) 23))
(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
@@ -167,9 +230,11 @@ repStartWithTag = \lyricmode {
repStopWithTag = \lyricmode {
\tag #'repeats {
\once \override StanzaNumber.details.custom-inline-text = \repStop
\once \override StanzaNumber.details.custom-inline-direction = #RIGHT
\once \override StanzaNumber.font-series = #'normal
\once \override StanzaNumber.direction = 1
\set stanza = \markup { \repStop }
\set stanza = \markup { \pad-x-right #1 \repStop }
}
}
@@ -253,3 +318,17 @@ rufWithMarkup =
ruf =
#(define-music-function (text) (string?)
(rufWithMarkup (make-ruf-style-markup text)))
underlineOn =
#(define-music-function () ()
#{
\override LyricText.stencil =
#(lambda (grob)
(grob-interpret-markup grob (make-underline-markup (ly:grob-property grob 'text))))
#})
underlineOff =
#(define-music-function () ()
#{
\revert LyricText.stencil
#})
+12 -60
View File
@@ -40,34 +40,19 @@ shiftChords = #(define-music-function (parser location xshift chords) (number? l
altChord =
#(define-music-function (parser location mainchord altchord) (ly:music? ly:music?)
(let* ((remove-point-and-click
(lambda (grob)
(ly:grob-set-property! grob 'cause #f)
(ly:text-interface::print grob)))
(chord-name (lambda (in-pitches bass inversion context) #{
\markup {
\translate #'(-0.5 . 0)
\score {
\chords { \transposable #(cons (car (music-pitches mainchord)) (car in-pitches)) { #(music-clone mainchord) \klamm #(music-clone altchord) } }
\layout {
\LAYOUT
\context {
\ChordNames
\override ChordName.extra-spacing-width = #'(0 . 0.3)
\override ChordName.stencil = #remove-point-and-click
}
\context {
\Score
\override SpacingSpanner.spacing-increment = 0
}
}
}
}#})))
(let* ((chord-name (lambda (in-pitches bass inversion context)
(make-line-markup (list
(ignatzek-chord-names in-pitches bass inversion context)
(make-hspace-markup 0.3)
(parenthesis-ignatzek-chord-names (music-pitches (transposable (cons (car (music-pitches mainchord)) (car in-pitches)) (music-clone altchord))) '() '() context)
))
)))
#{
\once \set chordNameFunction = #chord-name
#mainchord
#}))
% Akkorde werden so transponiert, dass sie passen, wenn man mit Kapo im angegebenen Bund spielt
capoTranspose =
#(define-music-function (fret chords) (number? ly:music?)
@@ -80,41 +65,8 @@ capoTranspose =
(ly:make-pitch 0 0)
chords))
% kleine Mollakkorde und Alteration ausgeschrieben
#(define (note-name->german-markup-nosym pitch lowercase?)
(define (pitch-alteration-semitones pitch) (inexact->exact (round (* (ly:pitch-alteration pitch) 2))))
(define (accidental->markup alteration name)
(if (= alteration 0)
(make-line-markup (list empty-markup))
(if (= alteration FLAT)
(if (equal? name "B")
""
; (make-line-markup (list (make-hspace-markup 0.2)
; (make-tiny-markup (make-raise-markup 1.2
; (make-musicglyph-markup (assoc-get alteration standard-alteration-glyph-name-alist ""))))
; ))
(if (or (equal? name "E") (equal? name "A")) "s" "es"))
"is")
))
(define (conditional-string-downcase str condition)
(if condition (string-downcase str) str))
(let* ((name (ly:pitch-notename pitch))
(alt-semitones (pitch-alteration-semitones pitch))
(n-a (if (member (cons name alt-semitones) `((6 . -1) (6 . -2)))
(cons 7 (+ 0 alt-semitones))
(cons name alt-semitones))))
(make-line-markup
(list
(make-simple-markup
(conditional-string-downcase
(vector-ref #("C" "D" "E" "F" "G" "A" "H" "B") (car n-a))
lowercase?))
(accidental->markup (/ (cdr n-a) 2) (vector-ref #("C" "D" "E" "F" "G" "A" "H" "B") (car n-a)) ))))
)
% additional bass notes should get uppercased
#(define (bassnote-name->german-markup-nosym pitch lowercase?)(note-name->german-markup-nosym pitch #f))
#(define (bassnote-name->german-markup-nosym pitch lowercase?)((chord-name:name-markup 'deutsch) pitch #f))
defaultChordPrintings = {
<c g>-\markup { \super "5" }
@@ -157,15 +109,15 @@ generalLayout = \layout {
\generalLayout
\context {
\ChordNames
\semiGermanChords
\override ChordName.font-size = \songScoreChordFontSize
\override ChordName.font-series = \songChordFontSeries
\override ChordName.font-family = #'serif
chordNameLowercaseMinor = ##t
chordChanges = ##t
% eigenen chordRootNamer damit F# = Fis und Gb = Ges (also alteration ausgeschrieben)
chordRootNamer = #note-name->german-markup-nosym
chordRootNamer = #(chord-name:name-markup 'deutsch)
chordNoteNamer = #bassnote-name->german-markup-nosym
additionalPitchPrefix = ""
majorSevenSymbol = "maj7"
chordNameExceptions = \chordNameExceptions
}
File diff suppressed because it is too large Load Diff
+550
View File
@@ -0,0 +1,550 @@
%%% Extrahiert Informationen aus einem ly:music-Objekt und liefert eine alist
%%% mit String-Werten zurueck (z.B. ((time . "4/4") (key . "d minor"))).
%%% Benoetigt LilyPond >= 2.26.
%% note-name->lily-string liegt im Modul (lily display-lily).
#(use-modules (lily display-lily))
#(begin
;; --- Taktangabe --------------------------------------------------------
;; Numerator einer Taktangabe kann eine Zahl oder eine Liste sein.
(define (numerator->string num)
(if (list? num)
(string-join (map number->string num) "+")
(number->string num)))
;; Ein einzelner Bruch ist ein Paar (numerator . denominator), der
;; Numerator eine Zahl oder eine Liste (z.B. 3+3/8).
(define (fraction->string frac)
(string-append (numerator->string (car frac)) "/"
(number->string (cdr frac))))
;; Liefert die Taktangabe als String (z.B. "4/4", "3+3/8" oder
;; "3+3/8 + 2/8") oder #f. time-signature ist ein Bruch oder bei
;; zusammengesetzten Taktarten (\timeAbbrev, \compoundMeter) eine
;; Liste von Bruechen.
(define (time-music->string m)
(let ((ts (ly:music-property m 'time-signature)))
(and (pair? ts)
(if (number? (cdr ts))
(fraction->string ts)
(string-join (map fraction->string ts) " + ")))))
;; --- Tempo ---------------------------------------------------------------
;; Liefert die Tempoangabe als String (z.B. "4 = 130", "4. = 60-70",
;; "Allegro" oder "Allegro (4 = 130)") oder #f.
(define (tempo-music->string m)
(let* ((unit (ly:music-property m 'tempo-unit))
(count (ly:music-property m 'metronome-count))
(text (ly:music-property m 'text))
(unit-s (and (ly:duration? unit)
(string-append
(number->string (expt 2 (ly:duration-log unit)))
(make-string (ly:duration-dot-count unit) #\.))))
(count-s (cond ((number? count) (number->string count))
((pair? count) (ly:format "~a-~a" (car count) (cdr count)))
(else #f)))
(metronome-s (and unit-s count-s (string-append unit-s " = " count-s)))
(text-s (and (markup? text) (markup->string text))))
(cond
((and text-s metronome-s) (string-append text-s " (" metronome-s ")"))
(metronome-s metronome-s)
(text-s text-s)
(else #f))))
;; --- Tonart ------------------------------------------------------------
;; Liefert die Tonart als String (z.B. "d minor" oder "c major") oder #f.
;; Vorgehen wie in LilyPonds eigenem \displayMusic (define-music-display-
;; methods.scm): pitch-alist nach c zuruecktransponieren und mit den
;; bekannten Modus-Definitionen vergleichen. note-name->lily-string liefert
;; den Grundton in der aktuell eingestellten Notensprache.
(define (key-music->string m)
(let ((tonic (ly:music-property m 'tonic))
(alist (ly:music-property m 'pitch-alist)))
(and (ly:pitch? tonic) (pair? alist)
(let* ((c-alist (ly:transpose-key-alist alist (- tonic)))
(mode (any (lambda (mode)
(and (equal? (ly:parser-lookup mode) c-alist)
(symbol->string mode)))
'(major minor))))
(and mode
(string-append (symbol->string (note-name->lily-string tonic))
" " mode))))))
;; --- Header-Metadaten ----------------------------------------------------
;; Header-Felder, die eigentlich Layoutsachen sind (gehoerten nach paper)
;; und deshalb nicht mit exportiert werden.
(define ignored-header-fields
'(titlesize titletopspace tagline between-poet-and-composer-markup))
;; categories ist im Header ein String mit Leerzeichen ("trist satz")
;; und wird als Liste von Woertern exportiert.
(define (header-value key value)
(if (and (eq? key 'categories) (string? value))
(string-tokenize value)
value))
;; Nimmt einen Bookpart entgegen (wie HEADER aus pool_bottom.ily) und
;; liefert dessen Header-Felder als alist, z.B.
;; ((title . "Ako umram") (authors . (("..." text melody))) ...).
;; Die Werte bleiben unveraendert, wie sie im \header definiert wurden
;; (Strings, Listen, Markups ...).
(define (bookpart->header-alist bookpart)
(let ((header (ly:book-header bookpart)))
(if (module? header)
(sort
(filter-map (lambda (entry)
(and (variable-bound? (cdr entry))
(not (memq (car entry) ignored-header-fields))
(cons (car entry)
(header-value (car entry)
(variable-ref (cdr entry))))))
(module-map cons header))
(lambda (a b)
(string<? (symbol->string (car a))
(symbol->string (car b)))))
'())))
;; --- Lyrics --------------------------------------------------------------
;; Silbentext (String oder Markup) als String. Eine Tilde verbindet als
;; Lyric-Bindebogen zwei Woerter auf einer Note und wird zum Leerzeichen.
(define (syllable->string text)
(let ((s (cond ((string? text) text)
((markup? text) (markup->string text))
(else ""))))
(string-map (lambda (c) (if (char=? c #\~) #\space c)) s)))
;; Stanza-Beschriftung, wie sie gedruckt wuerde; vgl. handle-stanza-numbers
;; bzw. refMarkupFormatter/bridgeMarkupFormatter in
;; basic_format_and_style_settings.ily. text ist ein direkt per
;; \set stanza = "..." gesetzter Wert und gewinnt. Mit roman? werden die
;; Nummern roemisch formatiert (\romanStanza, z.B. fremdsprachige
;; Originalstrophen).
(define (stanza-label type numbers text roman?)
(define (number->label n)
(if roman? (format #f "~@r" n) n))
(define (joined-numbers)
(string-join (map (lambda (n) (ly:format "~a" (number->label n))) numbers)
", "))
(define (formatted plain with-numbers)
(if (null? numbers)
(ly:parser-lookup plain)
(ly:format (ly:parser-lookup with-numbers) (joined-numbers))))
(cond
(text text)
((eq? type 'ref) (formatted 'refString 'refStringWithNumbers))
((eq? type 'bridge) (formatted 'bridgeString 'bridgeStringWithNumbers))
((pair? numbers)
(string-join
(map (lambda (n)
(format #f (ly:parser-lookup 'stanzaFormat) (number->label n)))
numbers)
", "))
(else "")))
;; Wandelt ein Lyrics-Music-Objekt (eine Zeile aus \addlyrics bzw. das
;; Argument von \chordlyrics) in eine Liste von Eintraegen der Form
;; (marked? nummern . ((stanza . "1.") (type . verse) (text . "Auf die ...")))
;; Silben werden an HyphenEvents wieder zu Woertern zusammengesetzt.
;; Strophengrenzen ergeben sich aus den StanzaNumber-Overrides von
;; #(stanza n), \ref und \bridge sowie aus \set stanza = "...".
;; marked? ist #f fuer Fortsetzungsfragmente: Abschnitte ohne Marker oder
;; mit dem Zeilenumbruch-Marker \set stanza = "-" (right-/leftHyphen).
;; \set stanza mit Markup-Wert (Wiederholungszeichen, "D.S." usw.) ist
;; reine Dekoration mitten im Vers und beginnt keine neue Strophe.
(define (lyric-line->stanzas lyrics)
(let ((stanzas '()) ; fertige Eintraege, rueckwaerts
(words '()) ; fertige Woerter der aktuellen Strophe, rueckwaerts
(word "") ; offene Silbenkette
(type #f) ; 'verse / 'ref / 'bridge
(numbers '()) ; Strophennummern
(set-label #f) ; direkt gesetztes \set stanza = "..."
(roman #f) ; \romanStanza: Nummern roemisch formatieren
(pending-override #f) ; \override-stanza vor dem naechsten Marker
(override-numbers #f)) ; gedruckte Nummern der aktuellen Strophe
(define (flush-word!)
(unless (string-null? word)
(set! words (cons word words))
(set! word "")))
(define (flush-stanza!)
(flush-word!)
(when (or type set-label (pair? words))
(let ((display-numbers (or override-numbers numbers)))
(set! stanzas
(cons (cons* (if (or type (and set-label (not (string=? set-label "-")))) #t #f)
display-numbers
(list (cons 'stanza (stanza-label type display-numbers set-label roman))
(cons 'type (or type 'verse))
(cons 'text (string-join (reverse words) " "))))
stanzas)))
(set! words '())
(set! type #f)
(set! numbers '())
(set! set-label #f)
(set! override-numbers #f)))
;; Ein neuer Marker beendet die laufende Strophe, sobald schon Text da ist.
(define (marker!)
(when (or (pair? words) (not (string-null? word)))
(flush-stanza!)))
(define (handle-lyric-event m)
(let ((s (syllable->string (ly:music-property m 'text)))
(hyphen? (any (lambda (a)
(eq? (ly:music-property a 'name) 'HyphenEvent))
(ly:music-property m 'articulations '()))))
;; "_"-Skips (Melisma-Platzhalter) kommen als " " an und werden
;; ignoriert; ein an einem Trennstrich offenes Wort ("ru -- _ hig")
;; laeuft darueber weiter.
(unless (string-null? (string-trim-both s))
(set! word (string-append word s))
(unless hyphen? (flush-word!)))))
(define (handle-override m)
(when (eq? (ly:music-property m 'symbol) 'StanzaNumber)
(let ((path (ly:music-property m 'grob-property-path))
(value (ly:music-property m 'grob-value)))
(cond
((equal? path '(details custom-stanza-type))
(marker!)
(set! type value)
;; \override-stanza wirkt (\once) auf den naechsten Marker
(set! override-numbers pending-override)
(set! pending-override #f))
((equal? path '(details custom-stanza-numbers))
(marker!)
(set! numbers (if (list? value) value (list value))))
((equal? path '(details custom-stanzanumber-override))
(set! pending-override (if (list? value) value (list value))))
((equal? path '(style))
(set! roman (eq? value 'roman)))))))
(define (handle-set m)
(let ((value (ly:music-property m 'value)))
;; Nur String-Werte; Markups (Wiederholungszeichen etc.) sind
;; Dekoration mitten im Vers und keine Strophengrenze.
(when (and (eq? (ly:music-property m 'symbol) 'stanza)
(string? value))
(marker!)
(set! set-label value))))
(define (walk m)
(case (ly:music-property m 'name)
((LyricEvent) (handle-lyric-event m))
((OverrideProperty) (handle-override m))
((PropertySet) (handle-set m))
(else
(let ((element (ly:music-property m 'element)))
(if (ly:music? element) (walk element))
(for-each (lambda (e) (if (ly:music? e) (walk e)))
(ly:music-property m 'elements))))))
(walk lyrics)
(flush-stanza!)
(merge-reprinted (reverse stanzas))))
;; Beim Zeilenumbruch im Notenbild wird die Strophennummer manchmal erneut
;; gedruckt (z.B. \firstVerseB #(stanza 1) \firstVerseC). Ein Marker
;; direkt nach einer Strophe mit gleicher Kennung oder deren
;; Nummern-Obermenge (#(stanza 2 3) nach Strophe 2) setzt diese fort,
;; statt eine neue Strophe zu beginnen.
(define (merge-reprinted entries)
(let loop ((remaining entries) (acc '()))
(if (null? remaining)
(reverse acc)
(let ((entry (car remaining)))
(if (and (pair? acc)
(car entry) ; aktueller Eintrag markiert
(car (car acc)) ; vorheriger Eintrag markiert
(let* ((prev (car acc))
(prev-alist (cddr prev))
(alist (cddr entry))
(prev-numbers (cadr prev))
(prev-label (assq-ref prev-alist 'stanza)))
(or (and (not (string-null? prev-label))
(string=? prev-label (assq-ref alist 'stanza)))
(and (eq? (assq-ref prev-alist 'type)
(assq-ref alist 'type))
(pair? prev-numbers)
(every (lambda (n) (member n (cadr entry)))
prev-numbers)))))
(begin
(append-fragment-text! (cddr (car acc)) (cddr entry))
(loop (cdr remaining) acc))
(loop (cdr remaining) (cons entry acc)))))))
;; Haengt den Text eines Fortsetzungsfragments an eine Strophe an.
(define (append-fragment-text! target fragment)
(let ((target-cell (assq 'text target))
(fragment-text (assq-ref fragment 'text)))
(unless (string-null? fragment-text)
(set-cdr! target-cell
(if (string-null? (cdr target-cell))
fragment-text
(string-append (cdr target-cell) " " fragment-text))))))
;; Fuegt die Strophen-Eintraege mehrerer Lyric-Zeilen (Ergebnisse von
;; lyric-line->stanzas) zu einer flachen Strophenliste zusammen. Manche
;; Lieder verteilen eine Strophe auf mehrere Zeilen unter den Noten
;; (Fragmente mit \set stanza = "-" bzw. ohne Marker): Fortsetzungen
;; werden innerhalb einer Zeile an die vorhergehende markierte Strophe
;; angehaengt; Zeilen ganz ohne markierte Strophen positionsweise an die
;; markierten Strophen der letzten Zeile, die welche hatte.
(define (merge-lyric-lines lines)
(let ((result '()) ; fertige Strophen-alists, rueckwaerts
(last-marked '())) ; markierte alists der letzten markierten Zeile
(for-each
(lambda (line)
(let ((line-marked (filter-map (lambda (e) (and (car e) (cddr e))) line)))
(if (and (null? line-marked) (pair? last-marked))
;; Zeile nur aus Fortsetzungen: k-tes Fragment an k-te
;; markierte Strophe der Vorzeile, Ueberzaehlige an deren letzte.
(let loop ((entries line) (targets last-marked))
(when (pair? entries)
(append-fragment-text! (car targets) (cddar entries))
(loop (cdr entries)
(if (null? (cdr targets)) targets (cdr targets)))))
;; Zeile mit markierten Strophen (oder erste Zeile ueberhaupt):
;; Fortsetzungen an die vorhergehende Strophe der Zeile.
(begin
(let loop ((entries line)
(prev (if (pair? last-marked) (last last-marked) #f)))
(when (pair? entries)
(let ((marked? (caar entries))
(stanza-alist (cddar entries)))
(if (or marked? (not prev))
(begin
(set! result (cons stanza-alist result))
(loop (cdr entries) stanza-alist))
(begin
(append-fragment-text! prev stanza-alist)
(loop (cdr entries) prev))))))
(when (pair? line-marked)
(set! last-marked line-marked))))))
lines)
(reverse result)))
;; Gruppiert alle LyricCombineMusic-Objekte im music-Baum nach ihrer
;; Stimme (associated-context, das \addlyrics/\lyricsto zuweisen).
;; Liefert eine Liste von Gruppen (je eine Liste von Lyrics-Music-
;; Objekten) in der Reihenfolge, in der die Stimmen zuerst auftauchen.
(define (voice-groups music)
(let* ((combines (extract-named-music music 'LyricCombineMusic))
(contexts (delete-duplicates
(map (lambda (c) (ly:music-property c 'associated-context))
combines))))
(map (lambda (ctx)
(map (lambda (c) (ly:music-property c 'element))
(filter (lambda (c)
(equal? (ly:music-property c 'associated-context) ctx))
combines)))
contexts)))
;; Liefert die Strophen aller Stimmen unter den Noten, die selbst echte
;; Strophen/Refrains/Bridges tragen (mindestens einen markierten
;; Abschnitt haben, vgl. lyric-line->stanzas) -- filtert damit rein
;; dekorative Gegen- oder Echostimmen heraus (z.B. wiederholte
;; Lautmalerei ohne eigene Strophennummer). "Die erste Stimme im
;; Dokument" ist dafuer kein verlaessliches Kriterium: welche Stimme
;; die Hauptmelodie mit dem vollstaendigen Strophentext singt, wechselt
;; von Lied zu Lied (mal Stimme 1, mal 2, je nach Satz).
;; Qualifizierende Stimmen werden zusammengefuehrt (verschiedene
;; Sprachfassungen tragen z.B. beide eigene Strophen); dabei bildet die
;; Stimme mit den meisten eigenen Strophen ('verse, nicht Ref/Bridge)
;; das Rueckgrat, in das die anderen einsortiert werden -- sonst wuerde
;; ein von mehreren Stimmen gemeinsam gesungener Refrain, der im
;; Dokument vor der eigentlichen Strophe auftaucht, deren Platz vor der
;; Strophe 1 belegen.
(define (main-voice-lyrics music)
(define (verse-count stanzas)
(count (lambda (e) (eq? (assq-ref e 'type) 'verse)) stanzas))
(define (any-marked? lines) (any (lambda (line) (any car line)) lines))
(let* ((groups (map (lambda (group) (map lyric-line->stanzas group))
(voice-groups music)))
(with-marks (filter any-marked? groups))
;; Der Ausschluss unmarkierter Stimmen lohnt nur, wenn es
;; ueberhaupt eine ALTERNATIVE gibt: hat KEINE Stimme einen
;; echten Strophenmarker, gibt es kein "Rueckgrat" zum
;; Einsortieren -- dann wie frueher nur die erste Stimme nehmen
;; (sonst wuerden z.B. mehrere Satzstimmen mit identischem,
;; unmarkiertem Text ihren Kehrreim mehrfach liefern, weil sich
;; unmarkierte Eintraege ohne Strophennummer nicht als Duplikat
;; erkennen lassen). Nur EINE Stimme insgesamt ist ein
;; Sonderfall davon (erste = einzige).
(qualifying (cond
((pair? with-marks) with-marks)
((pair? groups) (list (car groups)))
(else '())))
(merged-per-group (map merge-lyric-lines qualifying))
(ranked (sort (iota (length merged-per-group))
(lambda (a b) (> (verse-count (list-ref merged-per-group a))
(verse-count (list-ref merged-per-group b)))))))
(fold (lambda (i acc) (add-missing-stanzas acc (list-ref merged-per-group i)))
'() ranked)))
;; --- Textseiten-Strophen aus dem Render-Collector ------------------------
;; Liest die Strophen der Textseiten aus dem per-Song-Store (chordpro.ily),
;; den der ChordPro_score_collector beim Rendern der \chordlyrics-Scores
;; fuellt. Dort ist Tag-Filterung, \override-stanza usw. bereits von
;; LilyPond selbst entschieden -- unsichtbare Verse tauchen gar nicht
;; erst auf. Die Dokumentreihenfolge liefert
;; chordpro-song-ordered-verse-indices (die Interpretation laeuft
;; rueckwaerts und bei mehrspaltigen group-verses verschraenkt).
;; Liefert Eintraege wie lyric-line->stanzas:
;; ((stanza . "2.") (type . verse) (text . "Die Haeuser ..."))
(define (collector-song->stanzas song)
;; Silben eines Verses in Sammelreihenfolge; Eintrag: (moment text hyphen? idx)
(define (verse-syllables idx)
(reverse (filter (lambda (s) (= (cadddr s) idx))
(chordpro-song-syllables song))))
;; Gedruckte Labels koennen Wiederholungszeichen tragen ("2.𝄆") --
;; die gehoeren nicht ins Label.
(define (clean-label s)
(string-trim-both
(string-delete (lambda (c) (memv c '(#\𝄆 #\𝄇))) s)))
;; Silben zu Woertern: Trennstriche verbinden, "_"-Extender (kommen als
;; Whitespace an) werden ueberlaufen, ohne das offene Wort zu schliessen.
(define (syllables->text syls)
(let loop ((rest syls) (word "") (words '()))
(if (null? rest)
(string-join
(reverse (if (string-null? word) words (cons word words)))
" ")
(let* ((s (syllable->string (cadr (car rest))))
(hyphen? (caddr (car rest))))
(if (string-null? (string-trim-both s))
(loop (cdr rest) word words)
(if hyphen?
(loop (cdr rest) (string-append word s) words)
(loop (cdr rest) ""
(cons (string-append word s) words))))))))
(filter-map
(lambda (idx)
(let ((syls (verse-syllables idx))
(entry (assoc idx (chordpro-song-stanza-numbers song))))
(and (pair? syls)
(list (cons 'stanza (clean-label
(or (and entry (cadr entry)) "")))
(cons 'type (or (and entry (caddr entry)) 'verse))
(cons 'text (syllables->text syls))))))
(chordpro-song-ordered-verse-indices song)))
;; Dieselbe Strophe? Gleiche Kennung allein reicht nicht: zweisprachige
;; Lieder nutzen dieselben Nummern fuer Uebersetzung und Original. Die
;; Texte muessen sich auch aehneln (mindestens die Haelfte der Woerter
;; der kuerzeren Fassung kommt in der laengeren vor).
(define (same-stanza-text? a b)
(define (word-list s)
(map string-downcase (string-tokenize s char-set:letter)))
(let* ((wa (word-list a))
(wb (word-list b))
(shorter (if (<= (length wa) (length wb)) wa wb))
(longer (if (<= (length wa) (length wb)) wb wa)))
(or (null? shorter)
(>= (count (lambda (w) (member w longer)) shorter)
(/ (length shorter) 2)))))
;; Wie merge-stanza-lists, aber bei Konflikt gewinnt BASE statt der
;; einzumischenden Liste: fuer das Zusammenfuehren mehrerer Hauptstimmen-
;; Kandidaten (main-voice-lyrics), wo die 'reichste' Stimme (meiste
;; eigene Strophen) die kanonische Fassung liefert und andere Stimmen
;; nur FEHLENDE Strophen ergaenzen duerfen -- z.B. wenn Sopran und Alt
;; beide "1." tragen, der Alt aber eine gekuerzte Textvariante singt
;; (vgl. Sieh_des_Herbstes_Geisteshelle: infotext "Strophenteile in
;; Klammern sind die Textvarianten im Alt").
(define (add-missing-stanzas base additional)
(fold
(lambda (entry acc)
(let* ((key (assq-ref entry 'stanza))
(found (and (not (string-null? key))
(not (string=? key "-"))
(find (lambda (e) (and (equal? (assq-ref e 'stanza) key)
(same-stanza-text?
(assq-ref e 'text)
(assq-ref entry 'text))))
acc))))
(if found acc (append acc (list entry)))))
base
additional))
;; Fuegt Musik- und Textseiten-Strophen zusammen: gleiche Strophen nur
;; einmal, wobei die Fassung aus den Textseiten die unter den Noten
;; ersetzt (dort steht meist die vollstaendige Fassung).
(define (merge-stanza-lists music-stanzas text-stanzas)
(let loop ((merged music-stanzas) (remaining text-stanzas))
(if (null? remaining)
merged
(let* ((entry (car remaining))
(key (assq-ref entry 'stanza))
(found (and (not (string-null? key))
(not (string=? key "-"))
(find (lambda (e)
(and (equal? (assq-ref e 'stanza) key)
(same-stanza-text?
(assq-ref e 'text)
(assq-ref entry 'text))))
merged))))
(loop (if found
(map (lambda (e) (if (eq? e found) entry e)) merged)
(append merged (list entry)))
(cdr remaining))))))
;; Extrahiert alle Strophen/Refrain/Bridge-Texte eines Liedes: die Strophen
;; unter den Noten aus music (die Hauptstimme(n), siehe main-voice-lyrics),
;; die Textstrophen aus dem Render-Collector-Store des Liedes (song, darf
;; #f sein).
(define (extract-lyrics music song)
(merge-stanza-lists
(main-voice-lyrics music)
(if song (collector-song->stanzas song) '())))
;; --- Hauptfunktion -----------------------------------------------------
;; Liefert das erste Music-Objekt mit gegebenem Namen (oder #f).
(define (first-named-music music music-name)
(let ((matches (extract-named-music music music-name)))
(and (pair? matches) (car matches))))
;; Nimmt ly:music entgegen und liefert eine alist mit String-Werten.
(define (music->data-alist music)
(let* ((time-m (first-named-music
music '(TimeSignatureMusic ReferenceTimeSignatureMusic)))
(key-m (first-named-music music 'KeyChangeEvent))
(tempo-m (first-named-music music 'TempoChangeEvent))
(time-s (and time-m (time-music->string time-m)))
(key-s (and key-m (key-music->string key-m)))
(tempo-s (and tempo-m (tempo-music->string tempo-m))))
(append
(if time-s (list (cons 'time time-s)) '())
(if key-s (list (cons 'key key-s)) '())
(if tempo-s (list (cons 'tempo tempo-s)) '()))))
;; song ist der per-Song-Store-Eintrag aus chordpro.ily (oder #f, wenn
;; keine Textseiten gerendert wurden) -- er liefert die Textseiten-
;; Strophen. Darf erst NACH dem Rendern der Textseiten aufgerufen
;; werden (delayed stencil), vorher ist der Store leer. song-id ist
;; der Liedname (Einzellied: Ausgabename; Buch: der bei \includeSong
;; angegebene Dateiname) und landet unter dem Key song_id.
(define (extract-song-data header music song song-id)
(let ((header-alist (bookpart->header-alist header))
(music-alist (music->data-alist music))
(lyrics-list (extract-lyrics music song)))
(append header-alist
(list (cons 'song_id song-id)
(cons 'musical music-alist)
(cons 'lyrics lyrics-list)))))
;; Unsichtbarer Stencil fuer die Einzellied-YAML: schreibt <key>.yml
;; per delay-stencil-evaluation erst bei der Ausgabe, wenn der
;; Render-Collector alle Textseiten-Strophen gesammelt hat. header und
;; music werden beim Anhaengen (Parse-Zeit in layout_bottom) gebunden.
(define (yaml-delayed-song-write key header music)
(ly:make-stencil
`(delay-stencil-evaluation
,(delay (begin
(scm->yml-file (string-append key ".yml")
(extract-song-data header music
(hash-ref chordpro-song-store key)
key))
empty-stencil))))))
+130 -13
View File
@@ -2,13 +2,18 @@
poetPrefix = "Worte:"
composerPrefix = "Weise:"
compositionPrefix = "Satz:"
adaptionPrefix = "Bearbeitung:"
adaptionTextPrefix = "Bearbeitung:"
adaptionMusicPrefix = "Bearbeitung:"
poetAndComposerEqualPrefix = "Worte und Weise:"
voicePrefix = "Stimme:"
versePrefix = "Strophe:"
refPrefix = "Refrain:"
translationAuthorPrefix = "Übersetzung:"
translationPrefix = "Übersetzung:"
pronunciationPrefix = "Aussprache:"
interludePrefix = "Zwischenspiel:"
bridgePrefix = "Bridge:"
author-joiner = #(lambda (author-list) (string-join author-list ", "))
authorFormat =
#(lambda (noDetails name trail_name birth_year death_year organization)
@@ -28,6 +33,114 @@
""
)))
authorContributionFormat =
#(lambda* (render-contribution-group render-partial-contribution-group #:key
(poetIds '())
(translatorIds '())
(versePoetData '())
(composerIds '())
(verseComposerData '())
(refComposerIds '())
(voiceComposerData '())
(compositionIds '())
(adaptionTextIds '())
(adaptionMusicIds '())
(bridgeIds '())
(interludeIds '())
(year_text #f)
(year_translation #f)
(year_melody #f)
(year_melody_meloref #f)
(year_composition #f)
(year_adaption_text #f)
(year_adaption_music #f)
(poetAndComposerEqualPrefix "")
(poetPrefix "")
(composerPrefix "")
(translationAuthorPrefix "")
(pronunciationPrefix "")
(compositionPrefix "")
(adaptionTextPrefix "")
(adaptionMusicPrefix "")
(bridgePrefix "")
(interludePrefix "")
(refPrefix ""))
(if (and
(equal? poetIds composerIds)
(null? translatorIds)
(null? versePoetData)
(null? verseComposerData)
(null? refComposerIds)
(null? voiceComposerData)
(null? compositionIds)
(null? adaptionTextIds)
(null? adaptionMusicIds)
(null? bridgeIds)
(null? interludeIds))
(list
(join-present (list
(render-contribution-group poetAndComposerEqualPrefix poetIds)
(if (equal? year_text year_melody) year_text (join-present (list year_text year_melody) "/"))
) ", ")
#f)
(list
(if (and (null? poetIds) (null? versePoetData) (null? translatorIds)) #f
(string-append
poetPrefix
" "
(join-present (list
(join-present (list
(render-contribution-group "" poetIds)
year_text
) ", ")
(render-partial-contribution-group 'versePrefix versePoetData)
(join-present (list
(render-contribution-group adaptionTextPrefix adaptionTextIds)
year_adaption_text
) ", ")
(join-present (list
(render-contribution-group translationAuthorPrefix translatorIds)
year_translation
) ", ")
) "; ")
))
(if (and
(null? composerIds)
(null? compositionIds)
(null? adaptionMusicIds)
(null? verseComposerData)
(null? refComposerIds)
(null? voiceComposerData)
(null? bridgeIds)
(null? interludeIds)) #f
(string-append
composerPrefix
" "
(join-present (list
(join-present (list
(render-contribution-group "" composerIds)
year_melody
) ", ")
(render-partial-contribution-group 'versePrefix verseComposerData)
(join-present (list
(render-contribution-group refPrefix refComposerIds)
year_melody_meloref
) ", ")
(render-partial-contribution-group 'voicePrefix voiceComposerData)
(join-present (list
(render-contribution-group compositionPrefix compositionIds)
year_composition
) ", ")
(join-present (list
(render-contribution-group adaptionMusicPrefix adaptionMusicIds)
year_adaption_music
) ", ")
(render-contribution-group bridgePrefix bridgeIds)
(render-contribution-group interludePrefix interludeIds)
) "; ")
)))))
songinfoMarkup =
#(make-on-the-fly-markup
(lambda (layout props m)
@@ -42,31 +155,35 @@
(year_melody (chain-assoc-get 'songinfo:year_melody props #f))
(poet-with-year (if (and poet-maybe-with-composer year_text) (string-append poet-maybe-with-composer ", " year_text) poet-maybe-with-composer))
(composer-with-year (if (and composer year_melody) (string-append composer ", " year_melody) composer))
(poet-and-composer-oneliner (if (and poet-with-year composer-with-year) (markup poet-with-year between-poet-and-composer-markup composer-with-year) #f))
(current-line-width (chain-assoc-get 'line-width props (ly:output-def-lookup layout 'line-width)))
(concat-markupped-strings (lambda (text)
(ly:regex-replace (ly:make-regex "(\\S+)(\\\\\\w+(?:\\s+\\[^\\{\\s]*|\\s*\\{[^\\}]*\\}))(\\S*)") text "\\concat {" 1 "\\line {" 2 "}" 3 "}")))
(string-with-paragraphs->markuplist (lambda (prefix text)
(if text
(apply append
(map
(lambda (paragraph)
(make-wordwrap-internal-markup-list #t
#{ \markuplist { $(ly:parser-include-string paragraph) } #}))
(ly:regex-split (ly:make-regex "\n[ \t\n]*\n[ \t\n]*") (string-append prefix text))))
'()))))
#{ \markuplist { $(ly:parser-include-string (concat-markupped-strings paragraph)) } #}))
(ly:regex-split (ly:make-regex "\r?\n[ \t\r\n]*\n[ \t\r\n]*") (string-append prefix text))))
'())))
(poet-and-composer-markup-list
(string-with-paragraphs->markuplist "" (string-append
(if poet-with-year (string-append "\n\n" poet-with-year) "")
(if composer-with-year (string-append "\n\n" composer-with-year) "")
)))
(poet-and-composer-oneliner (if (and poet-with-year composer-with-year) (make-line-markup (cons (cadr poet-and-composer-markup-list) (cons between-poet-and-composer-markup (cddr poet-and-composer-markup-list)))) #f))
(current-line-width (chain-assoc-get 'line-width props (ly:output-def-lookup layout 'line-width))))
(stack-lines DOWN 0.0 (chain-assoc-get 'baseline-skip props)
(interpret-markup-list layout props
(append
(if (and poet-and-composer-oneliner (< (interval-length (ly:stencil-extent (interpret-markup layout props poet-and-composer-oneliner) X)) current-line-width))
(list poet-and-composer-oneliner)
(make-wordwrap-string-internal-markup-list #t (string-append
(if poet-with-year (string-append "\n\n" poet-with-year) "")
(if composer-with-year (string-append "\n\n" composer-with-year) "")
)))
(make-wordwrap-string-internal-markup-list #t (string-append
poet-and-composer-markup-list)
(string-with-paragraphs->markuplist "" (string-append
(if copyright (string-append "\n\n© " copyright) "")))
(string-with-paragraphs->markuplist "" infotext)
(string-with-paragraphs->markuplist "Übersetzung: " translation)
(string-with-paragraphs->markuplist "Aussprache: " pronunciation)
(string-with-paragraphs->markuplist (string-append (ly:output-def-lookup layout 'translationPrefix) " ") translation)
(string-with-paragraphs->markuplist (string-append (ly:output-def-lookup layout 'pronunciationPrefix) " ") pronunciation)
)))))
(make-null-markup)
)
+2 -1
View File
@@ -16,9 +16,10 @@ songTocColumns = 3
globalSize = 15
lyricSize = 1.6
stanzaFormat = "~a."
romanStanzaFormat = "~@r."
refString = "Ref.:"
refStringWithNumbers = "Ref. ~a:"
bridgeString = "Bridge:"
bridgeStringWithNumbers = "Bridge ~a:"
% hübsche Wiederholungszeichen für den Liedtext
repStart = "𝄆"
repStop = "𝄇"
@@ -6,10 +6,20 @@
#(define-markup-command (customEps layout props ysize filename)(number? string?)
#:properties ((songfilename ""))
(interpret-markup layout props
(let ((defaulttitlemarkup (ly:output-def-lookup layout 'defaultTitleMarkup))
(filepath (if (string-null? songfilename)
filename
(ly:format "~a/~a/~a" songPath songfilename filename))))
(let* ((defaulttitlemarkup (ly:output-def-lookup layout 'defaultTitleMarkup))
;; Determine the song directory to resolve the EPS relative to.
;; In TEXT the name arrives via the markup props (\setsongfilename),
;; but inside MUSIC (e.g. \mark) the props do not carry it, so we
;; fall back to the value stored in the score's \layout block.
(layout-songfilename (ly:output-def-lookup layout 'songfilename #f))
(songdir (cond ((not (string-null? songfilename)) songfilename)
((and (string? layout-songfilename)
(not (string-null? layout-songfilename)))
layout-songfilename)
(else #f)))
(filepath (if songdir
(ly:format "~a/~a/~a" songPath songdir filename)
filename)))
(if (file-exists? filepath)
(make-epsfile-markup Y ysize filepath)
(if defaulttitlemarkup
+50 -72
View File
@@ -30,6 +30,9 @@
(lambda (a b) (< (cadr a) (cadr b))))
(list)))
#(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) ()
(define (songinfo-from songId key)
(let ((song (if (defined? 'SONG_DATA) (assoc-ref SONG_DATA songId) #f)))
@@ -74,11 +77,7 @@
(define (render-contribution-group contributionPrefix authorIds)
(if (null? authorIds)
""
(string-append contributionPrefix " " (string-join (format-authors authorIds) ", ")))
)
(define (join-present items joiner)
(string-join (filter (lambda (item) (and (string? item) (not (string-null? (string-trim-both item))))) items) joiner)
(string-append contributionPrefix " " ((ly:output-def-lookup layout 'author-joiner) (format-authors authorIds))))
)
(define (render-partial-contribution-group prefixLookup authorData)
@@ -94,70 +93,39 @@
(define (poet-and-composer-from-authors authors)
(if authors
(let (
(poetIds (find-author-ids-by 'text authors))
(translatorIds (find-author-ids-by 'translation authors))
(versePoetData (find-author-id-with-part-numbers 'verse authors))
(composerIds (find-author-ids-by 'melody authors))
(verseComposerData (find-author-id-with-part-numbers 'meloverse authors))
(voiceComposerData (find-author-id-with-part-numbers 'voice authors))
(compositionIds (find-author-ids-by 'composition authors))
(adaptionIds (find-author-ids-by 'adaption authors))
(bridgeIds (find-author-ids-by 'bridge authors))
(interludeIds (find-author-ids-by 'interlude authors))
(year_text (chain-assoc-get 'header:year_text props #f))
(year_translation (chain-assoc-get 'header:year_translation props #f))
(year_melody (chain-assoc-get 'header:year_melody props #f))
(year_composition (chain-assoc-get 'header:year_composition props #f))
(year_adaption (chain-assoc-get 'header:year_adaption props #f))
)
(if (and (equal? poetIds composerIds) (null? translatorIds) (null? versePoetData) (null? verseComposerData) (null? voiceComposerData) (null? compositionIds) (null? adaptionIds) (null? bridgeIds) (null? interludeIds))
(list
(join-present (list
(render-contribution-group (ly:output-def-lookup layout 'poetAndComposerEqualPrefix) poetIds)
(if (equal? year_text year_melody) year_text (join-present (list year_text year_melody) "/"))
) ", ")
#f)
(list
(if (and (null? poetIds) (null? versePoetData) (null? translatorIds)) #f
(string-append
(ly:output-def-lookup layout 'poetPrefix)
" "
(join-present (list
(join-present (list
(render-contribution-group "" poetIds)
year_text
) ", ")
(render-partial-contribution-group 'versePrefix versePoetData)
(join-present (list
(render-contribution-group (ly:output-def-lookup layout 'translationPrefix) translatorIds)
year_translation
) ", ")
) "; ")
))
(if (and (null? composerIds) (null? compositionIds) (null? adaptionIds) (null? verseComposerData) (null? voiceComposerData) (null? bridgeIds) (null? interludeIds)) #f
(string-append
(ly:output-def-lookup layout 'composerPrefix)
" "
(join-present (list
(join-present (list
(render-contribution-group "" composerIds)
year_melody
) ", ")
(render-partial-contribution-group 'versePrefix verseComposerData)
(render-partial-contribution-group 'voicePrefix voiceComposerData)
(join-present (list
(render-contribution-group (ly:output-def-lookup layout 'compositionPrefix) compositionIds)
year_composition
) ", ")
(join-present (list
(render-contribution-group (ly:output-def-lookup layout 'adaptionPrefix) adaptionIds)
year_adaption
) ", ")
(render-contribution-group (ly:output-def-lookup layout 'bridgePrefix) bridgeIds)
(render-contribution-group (ly:output-def-lookup layout 'interludePrefix) interludeIds)
) "; ")
)))))
((ly:output-def-lookup layout 'authorContributionFormat)
render-contribution-group
render-partial-contribution-group
#:poetIds (find-author-ids-by 'text authors)
#:translatorIds (find-author-ids-by 'translation authors)
#:versePoetData (find-author-id-with-part-numbers 'verse authors)
#:composerIds (find-author-ids-by 'melody authors)
#:verseComposerData (find-author-id-with-part-numbers 'meloverse authors)
#:refComposerIds (find-author-ids-by 'meloref authors)
#:voiceComposerData (find-author-id-with-part-numbers 'voice authors)
#:compositionIds (find-author-ids-by 'composition authors)
#:adaptionTextIds (find-author-ids-by 'adaption_text authors)
#:adaptionMusicIds (find-author-ids-by 'adaption_music authors)
#:bridgeIds (find-author-ids-by 'bridge authors)
#:interludeIds (find-author-ids-by 'interlude authors)
#:year_text (chain-assoc-get 'header:year_text props #f)
#:year_translation (chain-assoc-get 'header:year_translation props #f)
#:year_melody (chain-assoc-get 'header:year_melody props #f)
#:year_melody_meloref (chain-assoc-get 'header:year_melody_meloref props #f)
#:year_composition (chain-assoc-get 'header:year_composition props #f)
#:year_adaption_text (chain-assoc-get 'header:year_adaption_text props #f)
#:year_adaption_music (chain-assoc-get 'header:year_adaption_music props #f)
#:poetAndComposerEqualPrefix (ly:output-def-lookup layout 'poetAndComposerEqualPrefix)
#:poetPrefix (ly:output-def-lookup layout 'poetPrefix)
#:composerPrefix (ly:output-def-lookup layout 'composerPrefix)
#:translationAuthorPrefix (ly:output-def-lookup layout 'translationAuthorPrefix)
#:pronunciationPrefix (ly:output-def-lookup layout 'pronunciationPrefix)
#:compositionPrefix (ly:output-def-lookup layout 'compositionPrefix)
#:adaptionTextPrefix (ly:output-def-lookup layout 'adaptionTextPrefix)
#:adaptionMusicPrefix (ly:output-def-lookup layout 'adaptionMusicPrefix)
#:bridgePrefix (ly:output-def-lookup layout 'bridgePrefix)
#:interludePrefix (ly:output-def-lookup layout 'interludePrefix)
#:refPrefix (ly:output-def-lookup layout 'refPrefix))
(list #f #f)
)
)
@@ -177,7 +145,7 @@
(translation (chain-assoc-get 'header:translation props #f))
(pronunciation (chain-assoc-get 'header:pronunciation props #f))
(year_text (if (string? (car poet-and-composers)) #f (chain-assoc-get 'header:year_text props #f)))
(year_melody (if (string? (car poet-and-composers)) #f (chain-assoc-get 'header:year_melody props #f))))
(year_melody (if (string? (cadr poet-and-composers)) #f (chain-assoc-get 'header:year_melody props #f))))
(markup
#:override (cons 'songinfo:poet-maybe-with-composer
(if (and poet-maybe-with-composer (not (and (string? poet-maybe-with-composer) (string-null? poet-maybe-with-composer)))) poet-maybe-with-composer #f))
@@ -219,13 +187,23 @@
`(line-width . ,(* (chain-assoc-get 'header:songinfo-size-factor props songInfoLineWidthFraction) (ly:output-def-lookup layout 'line-width)))
arg)))
#(define pdf-encode (@@ (lily framework-ps) pdf-encode))
% PDF tags
#(define-markup-command (page-number-to-pdf-label layout props) ()
(ly:make-stencil
(list 'embedded-ps
(ly:format
"[ /Label (~a) /PAGELABEL pdfmark\n" (pdf-encode (chain-assoc-get 'page:page-number-string props "?"))))
empty-interval empty-interval
))
\paper {
print-first-page-number = ##t
first-page-number = #0
oddFooterMarkup = \markup {
\fill-line {
\line { \null }
\line { \page-number-to-pdf-label \null }
\line { \if \on-last-page-of-part \general-align #Y #DOWN \fractional-line-width \print-songinfo }
\line { \if \should-print-page-number \print-pagenumber }
}
@@ -234,7 +212,7 @@
\fill-line {
\line { \if \should-print-page-number \print-pagenumber }
\line { \if \on-last-page-of-part \general-align #Y #DOWN \fractional-line-width \print-songinfo }
\line { \null }
\line { \page-number-to-pdf-label \null }
}
}
}
@@ -0,0 +1,16 @@
% We have to record the tag groups for markup, so we use the right tag groups during markup interpretiton.
recordedTagGroups = #'()
tagGroup =
#(define-void-function (tags) (symbol-list?)
(let ((err (define-tag-group tags)))
(if err (ly:parser-error err (*location*))
(set! recordedTagGroups (cons tags recordedTagGroups)))))
#(define-markup-command (handle-tag-groups layout props recorded-groups m) (list? markup?)
(resetTagGroups)
(for-each
(lambda (group)
(define-tag-group group))
recorded-groups)
(interpret-markup layout props m))
@@ -0,0 +1,86 @@
Better_Merge_rests_engraver =
#(lambda (context)
(define (has-one-or-less? lst) (or (null? lst) (null? (cdr lst))))
(define (has-at-least-two? lst) (not (has-one-or-less? lst)))
(define (all-equal? lst pred)
(or (has-one-or-less? lst)
(and (pred (car lst) (cadr lst)) (all-equal? (cdr lst) pred))))
(define (measure-count-eqv? a b)
(eqv?
(ly:grob-property a 'measure-count)
(ly:grob-property b 'measure-count)))
(define (rests-all-unpitched? rests)
"Returns true when all rests do not override the staff-position grob
property. When a rest has a position set we do not want to merge rests at
that position."
(every (lambda (rest) (null? (ly:grob-property rest 'staff-position))) rests))
(define (less-by-layer a b)
(<
(ly:grob-property b 'layer 0)
(ly:grob-property a 'layer 0)))
(define (merge-mmrests mmrests)
"Move all multimeasure rests to the single voice location."
(if (all-equal? mmrests measure-count-eqv?)
(begin
(for-each
(lambda (rest) (ly:grob-set-property! rest 'direction CENTER))
mmrests)
(for-each
(lambda (rest) (ly:grob-set-property! rest 'transparent #t))
(cdr (sort mmrests less-by-layer))))))
(define (merge-rests rests)
(for-each
(lambda (rest) (ly:grob-set-property! rest 'staff-position 0))
rests)
(for-each
(lambda (rest) (ly:grob-set-property! rest 'transparent #t))
(cdr (sort rests less-by-layer))))
(let ((mmrests '())
(rests '())
(dots '()))
(make-engraver
((start-translation-timestep translator)
(set! rests '())
(set! mmrests '())
(set! dots '()))
(acknowledgers
((dot-column-interface engraver grob source-engraver)
(if (not (ly:context-property context 'suspendRestMerging #f))
(set!
dots
(append (ly:grob-array->list (ly:grob-object grob 'dots))
dots))))
((rest-interface engraver grob source-engraver)
(cond
((ly:context-property context 'suspendRestMerging #f)
#f)
((grob::has-interface grob 'multi-measure-rest-interface)
(set! mmrests (cons grob mmrests)))
(else
(set! rests (cons grob rests))))))
((stop-translation-timestep translator)
(let (;; get a list of the rests 'duration-lengths, 'duration-log does
;; not take dots into account
(durs
(map
(lambda (g)
(ly:duration->moment
(ly:prob-property
(ly:grob-property g 'cause)
'duration)))
rests)))
(if (and
(has-at-least-two? rests)
(all-equal? durs equal?)
(rests-all-unpitched? rests))
(begin
(merge-rests rests)
;; ly:grob-suicide! works nicely for dots, as opposed to rests.
(if (pair? dots) (for-each ly:grob-suicide! (cdr dots)))))
(if (has-at-least-two? mmrests)
(merge-mmrests mmrests)))))))
-482
View File
@@ -1,482 +0,0 @@
;;; (json parser) --- Guile JSON implementation.
;; Copyright (C) 2013-2020 Aleix Conchillo Flaque <aconchillo@gmail.com>
;;
;; This file is part of guile-json.
;;
;; guile-json is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3 of the License, or
;; (at your option) any later version.
;;
;; guile-json is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with guile-json. If not, see https://www.gnu.org/licenses/.
;;; Commentary:
;; JSON module for Guile
;;; Code:
(define-module (json parser)
#:use-module (ice-9 rdelim)
#:use-module (ice-9 streams)
#:use-module (rnrs io ports)
#:export (json->scm
json-string->scm
json-seq->scm
json-seq-string->scm))
;;
;; Miscellaneuos helpers
;;
(define (json-exception port)
(throw 'json-invalid port))
(define (digit? c)
(case c
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) #t)
(else #f)))
(define (whitespace? c)
(case c
((#\sp #\ht #\lf #\cr) #t)
(else #f)))
(define (control-char? ch)
(<= (char->integer ch) #x1F))
(define (skip-whitespaces port)
(let ((ch (peek-char port)))
(cond
((whitespace? ch)
(read-char port)
(skip-whitespaces port))
(else *unspecified*))))
(define (expect-string port expected return)
(let loop ((n 0))
(cond
;; All characters match.
((= n (string-length expected)) return)
;; Go to next characters.
((eqv? (read-char port) (string-ref expected n))
(loop (+ n 1)))
;; Anything else is an error.
(else (json-exception port)))))
(define (expect-delimiter port delimiter)
(let ((ch (read-char port)))
(cond
((not (eqv? ch delimiter)) (json-exception port))
;; Unexpected EOF.
((eof-object? ch) (json-exception port)))))
(define (skip-record-separators port)
(when (eqv? #\rs (peek-char port))
(read-char port)
(skip-record-separators port)))
;;
;; Number parsing helpers
;;
(define (expect-digit port)
(let ((ch (peek-char port)))
(cond
((not (digit? ch)) (json-exception port))
;; Unexpected EOF.
((eof-object? ch) (json-exception port)))))
;; Read + or -, and return 1 or -1 respectively. If something different is
;; found, return 1.
(define (read-sign port)
(let ((ch (peek-char port)))
(cond
((eqv? ch #\+)
(read-char port)
1)
((eqv? ch #\-)
(read-char port)
-1)
(else 1))))
(define (read-digit-value port)
(let ((ch (read-char port)))
(cond
((eqv? ch #\0) 0)
((eqv? ch #\1) 1)
((eqv? ch #\2) 2)
((eqv? ch #\3) 3)
((eqv? ch #\4) 4)
((eqv? ch #\5) 5)
((eqv? ch #\6) 6)
((eqv? ch #\7) 7)
((eqv? ch #\8) 8)
((eqv? ch #\9) 9)
(else (json-exception port)))))
;; Read digits [0..9].
(define (read-digits port)
(expect-digit port)
(let loop ((ch (peek-char port)) (number 0))
(cond
((digit? ch)
(let ((value (read-digit-value port)))
(loop (peek-char port) (+ (* number 10) value))))
(else number))))
(define (read-digits-fraction port)
(expect-digit port)
(let loop ((ch (peek-char port)) (number 0) (length 0))
(cond
((digit? ch)
(let ((value (read-digit-value port)))
(loop (peek-char port) (+ (* number 10) value) (+ length 1))))
(else
(/ number (expt 10 length))))))
(define (read-exponent port)
(let ((ch (peek-char port)))
(cond
((or (eqv? ch #\e) (eqv? ch #\E))
(read-char port)
(let ((sign (read-sign port))
(digits (read-digits port)))
(if (<= digits 1000) ;; Some maximum exponent.
(expt 10 (* sign digits))
(json-exception port))))
(else 1))))
(define (read-fraction port)
(let ((ch (peek-char port)))
(cond
((eqv? ch #\.)
(read-char port)
(read-digits-fraction port))
(else 0))))
(define (read-positive-number port)
(let* ((number
(let ((ch (peek-char port)))
(cond
;; Numbers that start with 0 must be a fraction.
((eqv? ch #\0)
(read-char port)
0)
;; Otherwise read more digits.
(else (read-digits port)))))
(fraction (read-fraction port))
(exponent (read-exponent port))
(result (* (+ number fraction) exponent)))
(if (and (zero? fraction) (>= exponent 1))
result
(exact->inexact result))))
(define (json-read-number port)
(let ((ch (peek-char port)))
(cond
;; Negative numbers.
((eqv? ch #\-)
(read-char port)
(expect-digit port)
(* -1 (read-positive-number port)))
;; Positive numbers.
((digit? ch)
(read-positive-number port))
;; Anything else is an error.
(else (json-exception port)))))
;;
;; Object parsing helpers
;;
(define (read-pair port null ordered)
;; Read key.
(let ((key (json-read-string port)))
(skip-whitespaces port)
(let ((ch (peek-char port)))
(cond
;; Skip colon and read value.
((eqv? ch #\:)
(read-char port)
(cons key (json-read port null ordered)))
;; Anything other than colon is an error.
(else (json-exception port))))))
(define (json-read-object port null ordered)
(expect-delimiter port #\{)
(let loop ((pairs '()) (added #t))
(skip-whitespaces port)
(let ((ch (peek-char port)))
(cond
;; End of object.
((eqv? ch #\})
(read-char port)
(cond
(added (if ordered (reverse! pairs) pairs))
(else (json-exception port))))
;; Read one pair and continue.
((eqv? ch #\")
(let ((pair (read-pair port null ordered)))
(loop (cons pair pairs) #t)))
;; Skip comma and read more pairs.
((eqv? ch #\,)
(read-char port)
(cond
(added (loop pairs #f))
(else (json-exception port))))
;; Invalid object.
(else (json-exception port))))))
;;
;; Array parsing helpers
;;
(define (json-read-array port null ordered)
(expect-delimiter port #\[)
(skip-whitespaces port)
(cond
;; Special case when array is empty.
((eqv? (peek-char port) #\])
(read-char port)
#())
(else
;; Read first element in array.
(let loop ((values (list (json-read port null ordered))))
(skip-whitespaces port)
(let ((ch (peek-char port)))
(cond
;; Unexpected EOF.
((eof-object? ch) (json-exception port))
;; Handle comma (if there's a comma there should be another element).
((eqv? ch #\,)
(read-char port)
(loop (cons (json-read port null ordered) values)))
;; End of array.
((eqv? ch #\])
(read-char port)
(list->vector (reverse! values)))
;; Anything else other than comma and end of array is wrong.
(else (json-exception port))))))))
;;
;; String parsing helpers
;;
(define (read-hex-digit->integer port)
(let ((ch (read-char port)))
(cond
((eqv? ch #\0) 0)
((eqv? ch #\1) 1)
((eqv? ch #\2) 2)
((eqv? ch #\3) 3)
((eqv? ch #\4) 4)
((eqv? ch #\5) 5)
((eqv? ch #\6) 6)
((eqv? ch #\7) 7)
((eqv? ch #\8) 8)
((eqv? ch #\9) 9)
((or (eqv? ch #\A) (eqv? ch #\a)) 10)
((or (eqv? ch #\B) (eqv? ch #\b)) 11)
((or (eqv? ch #\C) (eqv? ch #\c)) 12)
((or (eqv? ch #\D) (eqv? ch #\d)) 13)
((or (eqv? ch #\E) (eqv? ch #\e)) 14)
((or (eqv? ch #\F) (eqv? ch #\f)) 15)
(else (json-exception port)))))
(define (read-unicode-value port)
(+ (* 4096 (read-hex-digit->integer port))
(* 256 (read-hex-digit->integer port))
(* 16 (read-hex-digit->integer port))
(read-hex-digit->integer port)))
;; Unicode codepoint with surrogates is:
;; 10000 + (high - D800) + (low - DC00)
;; which is equivalent to:
;; (high << 10) + low - 35FDC00
;; see
;; https://github.com/aconchillo/guile-json/issues/58#issuecomment-662744070
(define (json-surrogate-pair->unicode high low)
(+ (* high #x400) low #x-35FDC00))
(define (read-unicode-char port)
(let ((codepoint (read-unicode-value port)))
(cond
;; Surrogate pairs. `codepoint` already contains the higher surrogate
;; (between D800 and DC00) . At this point we are expecting another
;; \uXXXX that holds the lower surrogate (between DC00 and DFFF).
((and (>= codepoint #xD800) (< codepoint #xDC00))
(expect-string port "\\u" #f)
(let ((low-surrogate (read-unicode-value port)))
(if (and (>= low-surrogate #xDC00) (< low-surrogate #xE000))
(integer->char (json-surrogate-pair->unicode codepoint low-surrogate))
(json-exception port))))
;; Reserved for surrogates (we just need to check starting from the low
;; surrogates).
((and (>= codepoint #xDC00) (< codepoint #xE000))
(json-exception port))
(else (integer->char codepoint)))))
(define (read-control-char port)
(let ((ch (read-char port)))
(cond
((eqv? ch #\") #\")
((eqv? ch #\\) #\\)
((eqv? ch #\/) #\/)
((eqv? ch #\b) #\bs)
((eqv? ch #\f) #\ff)
((eqv? ch #\n) #\lf)
((eqv? ch #\r) #\cr)
((eqv? ch #\t) #\ht)
((eqv? ch #\u) (read-unicode-char port))
(else (json-exception port)))))
(define (json-read-string port)
(expect-delimiter port #\")
(let loop ((chars '()) (ch (read-char port)))
(cond
;; Unexpected EOF.
((eof-object? ch) (json-exception port))
;; Unescaped control characters are not allowed.
((control-char? ch) (json-exception port))
;; End of string.
((eqv? ch #\") (reverse-list->string chars))
;; Escaped characters.
((eqv? ch #\\)
(loop (cons (read-control-char port) chars) (read-char port)))
;; All other characters.
(else
(loop (cons ch chars) (read-char port))))))
;;
;; Booleans and null parsing helpers
;;
(define (json-read-true port)
(expect-string port "true" #t))
(define (json-read-false port)
(expect-string port "false" #f))
(define (json-read-null port null)
(expect-string port "null" null))
;;
;; Main parser functions
;;
(define (json-read port null ordered)
(skip-whitespaces port)
(let ((ch (peek-char port)))
(cond
;; Unexpected EOF.
((eof-object? ch) (json-exception port))
;; Read JSON values.
((eqv? ch #\t) (json-read-true port))
((eqv? ch #\f) (json-read-false port))
((eqv? ch #\n) (json-read-null port null))
((eqv? ch #\{) (json-read-object port null ordered))
((eqv? ch #\[) (json-read-array port null ordered))
((eqv? ch #\") (json-read-string port))
;; Anything else should be a number.
(else (json-read-number port)))))
;;
;; Public procedures
;;
(define* (json->scm #:optional (port (current-input-port))
#:key (null 'null) (ordered #f) (concatenated #f))
"Parse a JSON document into native. Takes one optional argument,
@var{port}, which defaults to the current input port from where the JSON
document is read. It also takes a few of keyword arguments: @{null}: value for
JSON's null, it defaults to the 'null symbol, @{ordered} to indicate whether
JSON objects order should be preserved or not (the default) and @{concatenated}
which can be used to tell the parser that more JSON documents might come after a
properly parsed document."
(let loop ((value (json-read port null ordered)))
;; Skip any trailing whitespaces.
(skip-whitespaces port)
(cond
;; If we reach the end the parsing succeeded.
((eof-object? (peek-char port)) value)
;; If there's anything else other than the end, check if user wants to keep
;; parsing concatenated valid JSON documents, otherwise parser fails.
(else
(cond (concatenated value)
(else (json-exception port)))))))
(define* (json-string->scm str #:key (null 'null) (ordered #f))
"Parse a JSON document into native. Takes a string argument,
@var{str}, that contains the JSON document. It also takes a couple of keyword
argument: @{null}: value for JSON's null, it defaults to the 'null symbol and
@{ordered} to indicate whether JSON objects order should be preserved or
not (the default)."
(call-with-input-string str (lambda (p) (json->scm p #:null null #:ordered ordered))))
(define* (json-seq->scm #:optional (port (current-input-port))
#:key (null 'null) (ordered #f)
(handle-truncate 'skip) (truncated-object 'truncated))
"Lazy parse a JSON text sequence from the port @var{port}.
This procedure returns a stream of parsed documents. The optional argument
@var{port} defines the port to read from and defaults to the current input
port. It also takes a few keyword arguments: @{null}: value for JSON's null
(defaults to the 'null symbol), @{ordered} to indicate whether JSON objects
order should be preserved or not (the default), @{handle-truncate}: defines how
to handle data loss, @{truncated-object}: used to replace unparsable
objects. Allowed values for @{handle-truncate} argument are 'throw (throw an
exception), 'stop (stop parsing and end the stream), 'skip (default, skip
corrupted fragment and return the next entry), 'replace (skip corrupted fragment
and return @{truncated-object} instead)."
(letrec ((handle-truncation
(case handle-truncate
((throw) json-exception)
((stop) (const (eof-object)))
((skip)
(lambda (port)
(read-delimited "\x1e" port 'peek)
(read-entry port)))
((replace)
(lambda (port)
(read-delimited "\x1e" port 'peek)
truncated-object))))
(read-entry
(lambda (port)
(let ((ch (read-char port)))
(cond
((eof-object? ch) ch)
((not (eqv? ch #\rs))
(handle-truncation port))
(else
(skip-record-separators port)
(catch 'json-invalid
(lambda ()
(let ((next (json-read port null ordered)))
(if (eqv? #\lf (peek-char port))
(begin
(read-char port)
next)
(handle-truncation port))))
(lambda (_ port)
(handle-truncation port)))))))))
(port->stream port read-entry)))
(define* (json-seq-string->scm str #:key (null 'null) (ordered #f)
(handle-truncate 'skip) (truncated-object 'truncated))
"Lazy parse a JSON text sequence from the string @var{str}.
This procedure returns a stream of parsed documents and also takes the same
keyword arguments as @code{json-seq->scm}."
(call-with-input-string str
(lambda (p)
(json-seq->scm p #:null null #:ordered ordered
#:handle-truncate handle-truncate
#:truncated-object truncated-object))))
;;; (json parser) ends here
+255 -21
View File
@@ -1,25 +1,259 @@
(if (not windows?) (use-modules (ice-9 popen)))
(use-modules (ice-9 textual-ports) (json parser))
(use-modules (ice-9 rdelim) (ice-9 regex) (ice-9 receive) (srfi srfi-1))
;; YAML-Parser: liest eine Teilmenge von YAML (Block-Syntax, keine
;; Flow-Collections, keine Block-Skalare) in verschachtelte
;; alist/list-Strukturen. Gegenstück zu yaml_writer.scm.
;;
;; Ergebnisformat:
;; - Mapping -> alist mit String-Keys, Reihenfolge wie in der Datei
;; - Liste -> Liste (auch als "- key: value"-Inline-Mappings)
;; - Skalare -> String, Zahl, #t/#f, '() (null / {} / [])
;;
;; Skalar-Interpretation:
;; - unquoted und 'single-quoted' Werte werden interpretiert
;; (Zahl, true/false/null). Das Interpretieren von single-quoted
;; Werten ist Kompatibilitätsverhalten zum alten Parser:
;; authors.yml notiert Zahlen als '1898'.
;; - "double-quoted" Werte bleiben immer Strings; \\, \" und \n
;; werden entescaped (so schreibt sie der yaml_writer).
;; - Kommentare (#) werden nur außerhalb von Quotes entfernt und nur,
;; wenn ihnen ein Leerzeichen oder der Zeilenanfang vorausgeht.
; We use Python to convert the data yamls like the authors.yml to json, that we can parse in scheme.
; Windows does not like Pipes, so we use a tmpfile instead.
; Be sure you have PyYAML installed. On Windows that could be done for example like "py -m pip install PyYAML"
(define (yml-file->scm filename)
(if windows?
(let* ((port (make-tmpfile #f))
(tmpfilepath (port-filename port))
(ignore (close-port port))
(python_code (string-append "import sys, yaml, json; f = open(r'" tmpfilepath "', 'w'); f.write(json.dumps(yaml.safe_load(open(r'" filename "')))); f.close()"))
(status (system (string-append (search-executable '("python3" "python" "py")) " -X utf8 -c \"" python_code "\"")))
(readport (open-file tmpfilepath "r" #:encoding "UTF-8"))
(json (get-string-all readport)))
(close-port readport)
(delete-file tmpfilepath)
(json-string->scm (if (status:exit-val status) json "{}")))
(let* ((python_code (string-append "import sys, yaml, json; print(json.dumps(yaml.safe_load(open(r'" filename "'))))"))
(pipe (open-pipe (string-append "PYTHONHOME='' " (search-executable '("python3" "python" "py")) " -X utf8 -c \"" python_code "\"") OPEN_READ))
(json (get-string-all pipe)))
(close-pipe pipe)
(json-string->scm json))))
;; --- Zeilen einlesen ------------------------------------------------
;; Anzahl führender Leerzeichen
(define (line-indent line)
(let loop ((i 0))
(if (and (< i (string-length line))
(char=? (string-ref line i) #\space))
(loop (+ i 1))
i)))
;; Liefert Items (indent . inhalt); Leerzeilen, reine Kommentarzeilen
;; und Dokumentmarker (--- / ...) werden übersprungen.
(define (read-items filename)
(call-with-input-file filename
(lambda (port)
(let loop ((items '()))
(let ((line (read-line port)))
(if (eof-object? line)
(reverse items)
(let* ((line (if (string-suffix? "\r" line)
(string-drop-right line 1)
line))
(content (string-trim-both line)))
(if (or (string-null? content)
(string-prefix? "#" content)
(string=? content "---")
(string=? content "..."))
(loop items)
(loop (cons (cons (line-indent line) content)
items))))))))))
(define (item-indent item) (car item))
(define (item-content item) (cdr item))
;; --- Skalare ----------------------------------------------------------
;; Position des schließenden Double-Quotes ab start (oder #f);
;; Backslash escaped das Folgezeichen.
(define (closing-double-quote s start)
(let loop ((i start))
(cond ((>= i (string-length s)) #f)
((char=? (string-ref s i) #\\) (loop (+ i 2)))
((char=? (string-ref s i) #\") i)
(else (loop (+ i 1))))))
;; Position des schließenden Single-Quotes ab start (oder #f);
;; '' ist ein escaptes Quote.
(define (closing-single-quote s start)
(let loop ((i start))
(cond ((>= i (string-length s)) #f)
((char=? (string-ref s i) #\')
(if (and (< (+ i 1) (string-length s))
(char=? (string-ref s (+ i 1)) #\'))
(loop (+ i 2))
i))
(else (loop (+ i 1))))))
;; \\ -> \, \" -> ", \n -> Zeilenumbruch
(define (unescape-double s)
(let loop ((chars (string->list s)) (acc '()))
(cond ((null? chars) (list->string (reverse acc)))
((and (char=? (car chars) #\\) (pair? (cdr chars)))
(loop (cddr chars)
(cons (if (char=? (cadr chars) #\n)
#\newline
(cadr chars))
acc)))
(else (loop (cdr chars) (cons (car chars) acc))))))
;; '' -> '
(define (unescape-single s)
(regexp-substitute/global #f "''" s 'pre "'" 'post))
;; Kommentar in einem unquoted Wert entfernen:
;; # zählt nur am Anfang oder nach Leerzeichen
(define (strip-plain-comment s)
(let loop ((i 0))
(cond ((>= i (string-length s)) s)
((and (char=? (string-ref s i) #\#)
(or (zero? i)
(char-whitespace? (string-ref s (- i 1)))))
(string-take s i))
(else (loop (+ i 1))))))
;; Unquoted/single-quoted Werte interpretieren
(define (interpret-plain s)
(cond
((member s '("{}" "[]" "null")) '())
((string=? s "true") #t)
((string=? s "false") #f)
((string-match "^-?[0-9]+(\\.[0-9]+)?$" s) (string->number s))
(else s)))
;; Skalar parsen; Rest hinter einem schließenden Quote wird ignoriert
;; (darf nur ein Kommentar sein)
(define (parse-scalar str)
(let ((s (string-trim-both str)))
(cond
((string-null? s) "")
((char=? (string-ref s 0) #\")
(let ((end (closing-double-quote s 1)))
(if end
(unescape-double (substring s 1 end))
(string-trim-right (strip-plain-comment s)))))
((char=? (string-ref s 0) #\')
(let ((end (closing-single-quote s 1)))
(if end
(interpret-plain (unescape-single (substring s 1 end)))
(string-trim-right (strip-plain-comment s)))))
(else
(interpret-plain (string-trim-right (strip-plain-comment s)))))))
;; --- Struktur ----------------------------------------------------------
;; Erste Key-Trennstelle: ":" am Zeilenende oder gefolgt von Leerzeichen.
;; Beginnt die Zeile mit einem gequoteten Key (oder Skalar), zaehlt ein
;; ":" innerhalb der Quotes nicht als Trennstelle.
(define (key-split-index s)
(let ((start (if (string-null? s)
0
(case (string-ref s 0)
((#\") (let ((end (closing-double-quote s 1)))
(if end (+ end 1) 0)))
((#\') (let ((end (closing-single-quote s 1)))
(if end (+ end 1) 0)))
(else 0)))))
(let loop ((i start))
(cond ((>= i (string-length s)) #f)
((and (char=? (string-ref s i) #\:)
(or (= i (- (string-length s) 1))
(char=? (string-ref s (+ i 1)) #\space)))
i)
(else (loop (+ i 1)))))))
;; Key interpretieren: gequotete Keys werden entquotet (Strings bleiben
;; Strings), unquoted Keys bleiben unveraendert
(define (parse-key s)
(let ((k (string-trim-both s)))
(cond
((string-null? k) k)
((char=? (string-ref k 0) #\")
(let ((end (closing-double-quote k 1)))
(if end (unescape-double (substring k 1 end)) k)))
((char=? (string-ref k 0) #\')
(let ((end (closing-single-quote k 1)))
(if end (unescape-single (substring k 1 end)) k)))
(else k))))
(define (dash-line? content)
(or (string=? content "-") (string-prefix? "- " content)))
;; Wert-String hinter "key:" bzw. "-": reiner Kommentar zählt als leer
(define (effective-value str)
(let ((s (string-trim-both str)))
(if (string-prefix? "#" s) "" s)))
;; Einen Block von Items parsen; das erste Item bestimmt die Blockart
(define (parse-block items)
(cond
((null? items) '())
((dash-line? (item-content (car items))) (parse-list items))
;; einzelne Zeile ohne Key: Skalar (z.B. eine Datei, die nur {} enthält)
((and (null? (cdr items))
(not (key-split-index (item-content (car items)))))
(parse-scalar (item-content (car items))))
(else (parse-map items))))
;; Mapping: alle Items auf map-indent sind Keys, tiefer eingerückte
;; Items gehören zum jeweils vorangehenden Key
(define (parse-map items)
(let ((map-indent (item-indent (car items))))
(let loop ((items items) (result '()))
(if (null? items)
(reverse result)
(let* ((content (item-content (car items)))
(split (key-split-index content)))
(if (not split)
(begin
(format (current-error-port)
"YAML-Syntaxfehler: Ungültige Zeile: ~a\n" content)
(loop (cdr items) result))
(let ((key (parse-key (string-take content split)))
(value-str (effective-value
(string-drop content (+ split 1)))))
(if (string-null? value-str)
;; Wert steht im eingerückten Block darunter
(receive (children rest)
(span (lambda (it) (> (item-indent it) map-indent))
(cdr items))
(loop rest
(cons (cons key (parse-block children))
result)))
(loop (cdr items)
(cons (cons key (parse-scalar value-str))
result))))))))))
;; Liste: "- wert", "- key: value" (Inline-Mapping) oder "-" mit
;; eingerücktem Block darunter
(define (parse-list items)
(let ((list-indent (item-indent (car items))))
(let loop ((items items) (result '()))
(if (null? items)
(reverse result)
(let ((content (item-content (car items))))
(if (not (dash-line? content))
(begin
(format (current-error-port)
"YAML-Syntaxfehler: Ungültiges Listenelement: ~a\n"
content)
(loop (cdr items) result))
(receive (children rest)
(span (lambda (it) (> (item-indent it) list-indent))
(cdr items))
(let* ((after-dash (string-drop content 1))
(inline (effective-value after-dash)))
(cond
((and (string-null? inline) (null? children))
(loop rest (cons '() result)))
((string-null? inline)
(loop rest (cons (parse-block children) result)))
(else
;; Inline-Inhalt: als synthetisches Item mit der
;; Spalte des Inhalts vor die Kinder stellen, so
;; funktioniert auch "- key: value" mit weiteren
;; Keys auf den Folgezeilen
(let* ((offset (- (string-length content)
(string-length
(string-trim after-dash))))
(synth (cons (+ list-indent offset) inline)))
(loop rest
(cons (parse-block (cons synth children))
result)))))))))))))
(parse-block (read-items filename)))
(define (parse-yml-file filename) (resolve-inherits (yml-file->scm filename)))
+162
View File
@@ -0,0 +1,162 @@
(use-modules (ice-9 regex) (srfi srfi-1))
;; YAML-Writer: Gegenstück zu yaml_parser.scm.
;; Schreibt verschachtelte alist/list-Strukturen als YAML, so dass
;; yml-file->scm sie wieder einlesen kann.
;;
;; Datenmodell (wie vom Parser erzeugt):
;; - Mapping: Liste von Paaren (key . value), key als String oder Symbol
;; - Liste: Liste von beliebigen Werten
;; - Skalare: String, Zahl, #t/#f, '() (leer/null, wird als [] geschrieben)
;;
;; Andere Werte werden vorab automatisch umgewandelt: Markups zu Strings,
;; alles Unbekannte über ~a formatiert (siehe yml-sanitize).
;; Steuert, ob layout_bottom.ily die Header-Daten des Liedes als
;; YAML-Datei exportiert; aktivierbar per (set! yaml-export-enabled #t)
;; oder durch Definieren der Variable vor dem Laden der Includes.
(define yaml-export-enabled
(if (defined? 'yaml-export-enabled) yaml-export-enabled #f))
;; Beliebige Werte in YAML-taugliche Daten umwandeln.
;; (Benötigt die LilyPond-Umgebung für markup? und markup->string.)
(define (yml-sanitize v)
(cond
((or (string? v) (number? v) (boolean? v) (symbol? v) (null? v)) v)
((markup? v) (markup->string v))
((list? v) (map yml-sanitize v))
((pair? v) (cons (yml-sanitize (car v)) (yml-sanitize (cdr v))))
(else (format #f "~a" v))))
(define (scm->yml-string data)
;; Ist data ein Mapping (alist)? Nicht-leere Liste, deren Elemente
;; alle Paare mit String- oder Symbol-Key sind.
;; Achtung: eine Liste von Listen, deren erste Elemente Strings sind,
;; ist davon nicht unterscheidbar und wird als Mapping interpretiert.
(define (mapping? data)
(and (pair? data)
(list? data)
(every (lambda (entry)
(and (pair? entry)
(or (string? (car entry))
(symbol? (car entry)))))
data)))
(define (scalar? v)
(or (string? v) (symbol? v) (number? v) (boolean? v) (null? v)))
(define (key->string k)
(if (symbol? k) (symbol->string k) k))
;; Muss der String gequotet werden, damit er beim Einlesen wieder
;; als derselbe String erkannt wird?
(define (needs-quotes? s)
(or (string-null? s)
(member s '("true" "false" "null" "{}" "[]"))
(string->number s) ;; zahlartig ("1981", "1.", "-5") würde zur Zahl
(string-index s #\#) ;; würde als Kommentar abgeschnitten
(string-index s #\:) ;; würde als Key: Value gelesen
(string-index s #\newline) ;; mehrzeilig
(string-index s #\")
(string-prefix? "-" s) ;; würde als Listenelement gelesen
(string-prefix? "'" s)
(not (string=? s (string-trim-both s))))) ;; führende/folgende Leerzeichen
;; Doppelt gequoteter YAML-String; Backslash, Anführungszeichen und
;; Zeilenumbrüche werden escaped, damit alles auf einer Zeile bleibt.
(define (quote-string s)
(string-append
"\""
(string-concatenate
(map (lambda (c)
(cond
((char=? c #\\) "\\\\")
((char=? c #\") "\\\"")
((char=? c #\newline) "\\n")
(else (string c))))
(string->list s)))
"\""))
(define (scalar->string v)
(cond
((eq? v #t) "true")
((eq? v #f) "false")
((null? v) "[]")
((number? v) (number->string v))
((symbol? v) (scalar->string (symbol->string v)))
((string? v) (if (needs-quotes? v) (quote-string v) v))
(else (error "scm->yml-string: nicht unterstützter Wert" v))))
(define (indent-string n) (make-string n #\space))
;; Key ggf. quoten (leere oder zahlartige Keys, Sonderzeichen)
(define (key->yml-string k)
(let ((s (key->string k)))
(if (needs-quotes? s) (quote-string s) s)))
;; Doppelte Keys sind in YAML nicht erlaubt: Werte gleicher Keys werden
;; zu einer Liste zusammengefasst, z.B. authors mit (voice 2) (voice 3)
;; -> voice: [2, 3].
(define (merge-duplicate-keys data)
(define (value->list v)
(if (and (list? v) (not (mapping? v))) v (list v)))
(let loop ((entries data) (result '()))
(if (null? entries)
(reverse result)
(let* ((entry (car entries))
(existing (find (lambda (e) (equal? (car e) (car entry)))
result)))
(if existing
(begin
(set-cdr! existing (append (value->list (cdr existing))
(value->list (cdr entry))))
(loop (cdr entries) result))
(loop (cdr entries)
(cons (cons (car entry) (cdr entry)) result)))))))
;; Mapping schreiben: "key: skalar" oder "key:" mit eingerücktem Inhalt
(define (write-mapping data indent port)
(for-each
(lambda (entry)
(let ((key (key->yml-string (car entry)))
(value (cdr entry)))
(if (scalar? value)
(format port "~a~a: ~a\n"
(indent-string indent) key (scalar->string value))
(begin
(format port "~a~a:\n" (indent-string indent) key)
(write-node value (+ indent 2) port)))))
(merge-duplicate-keys data)))
;; Liste schreiben: "- skalar" oder "-" mit eingerücktem Inhalt
;; (Verschachtelter Inhalt darf NICHT auf der "-"-Zeile beginnen,
;; weil parse-list den Inhalt der "-"-Zeile verwirft, sobald
;; eingerückte Folgezeilen existieren.)
(define (write-list data indent port)
(for-each
(lambda (item)
(if (scalar? item)
(format port "~a- ~a\n"
(indent-string indent) (scalar->string item))
(begin
(format port "~a-\n" (indent-string indent))
(write-node item (+ indent 2) port))))
data))
(define (write-node data indent port)
(cond
((mapping? data) (write-mapping data indent port))
((and (list? data) (not (null? data))) (write-list data indent port))
(else (format port "~a~a\n" (indent-string indent) (scalar->string data)))))
(call-with-output-string
(lambda (port) (write-node (yml-sanitize data) 0 port))))
;; Daten als YAML-Datei schreiben
(define (scm->yml-file filename data)
(call-with-output-file filename
(lambda (port)
(display "---\n" port)
(display (scm->yml-string data) port))))
+3 -3
View File
@@ -1,4 +1,4 @@
swing = \mark \markup {
swing = \textMark \markup {
\line \general-align #Y #DOWN {
\score {
\new Staff \with {
@@ -47,7 +47,7 @@ swing = \mark \markup {
}
}
swingOff = \mark \markup {
swingOff = \textMark \markup {
\line \general-align #Y #DOWN {
\score {
\new Staff \with {
@@ -99,7 +99,7 @@ swingOff = \mark \markup {
\include "swing.ly"
swingMusic =
#(define-music-function (parser location music) (ly:music?)
#(define-music-function (music) (ly:music?)
(define (partial-duration-length m)
(let ((name (ly:music-property m 'name))
(es (ly:music-property m 'elements))
@@ -1,6 +1,5 @@
#(define-markup-command (bookTitleMarkupCustom layout props)()
(interpret-markup layout
(prepend-alist-chain 'songfilename (chain-assoc-get 'header:songfilename props "") props)
(interpret-markup layout props
(make-column-markup
(list
(make-vspace-markup (chain-assoc-get 'header:titletopspace props 0))
@@ -27,32 +26,59 @@
#(define pdf-encode (@@ (lily framework-ps) pdf-encode))
% PDF tags
#(define-markup-command (title-to-pdf-toc layout props title) (string?)
% Erzeugt ein PDF-Lesezeichen (Outline-Eintrag) auf der aktuellen Seite.
% count = Anzahl der direkten Unterpunkte, die (in Seitenreihenfolge) direkt
% nachfolgen. 0 = Blatt-Eintrag (kein /Count), >0 = aufgeklappt,
% <0 = zugeklappt (Betrag = Anzahl direkter Kinder).
#(define (make-pdf-toc-stencil title count)
(if (string-null? title)
empty-stencil
(ly:make-stencil
(list 'embedded-ps
(ly:format
"[/Action /GoTo /View [/XYZ -4 currentpagedevice /PageSize get 1 get 4 add null] /Title (~a) /OUT pdfmark" (pdf-encode title)))
"[~a/Action /GoTo /View [/XYZ -4 currentpagedevice /PageSize get 1 get 4 add null] /Title (~a) /OUT pdfmark"
(if (= count 0) "" (ly:format "/Count ~a " count))
(pdf-encode title)))
empty-interval empty-interval
;'(0 . 0) '(0 . 0)
))
)))
#(define-markup-command (title-with-category-images layout props right)(boolean?)
(interpret-markup layout props
% Einzelner (Blatt-)Eintrag im PDF-Inhaltsverzeichnis.
#(define-markup-command (title-to-pdf-toc layout props title) (string?)
(make-pdf-toc-stencil title 0))
% Kapitel-Eintrag mit verschachtelten Unterpunkten.
% count = Anzahl der direkt nachfolgenden Einträge, die diesem Kapitel
% untergeordnet werden sollen (positiv = aufgeklappt, negativ = zugeklappt).
#(define-markup-command (chapter-to-pdf-toc layout props count title) (integer? string?)
(make-pdf-toc-stencil title count))
#(define-markup-command (build-full-title layout props right)(boolean?)
(interpret-markup layout (prepend-alist-chain 'songfilename (chain-assoc-get 'header:songfilename props "") props)
(let* ((title (chain-assoc-get 'header:title props ""))
(starttext (chain-assoc-get 'header:starttext props #f))
(pdfbookmark (if starttext (string-append starttext " | " title) title)))
(pdfbookmark (if starttext (string-append starttext " | " title) title))
(chaptertitle (chain-assoc-get 'header:chaptertitle props #f))
(chaptercount (chain-assoc-get 'header:chaptercount props 0))
(title-markup (ly:output-def-lookup layout (if right 'oddTitleLineMarkup 'evenTitleLineMarkup))))
(if title
(if right
#{\markup { \title-to-pdf-toc #pdfbookmark \fill-line \general-align #Y #UP { \null \bookTitleMarkupCustom \category-images } } #}
#{\markup { \title-to-pdf-toc #pdfbookmark \fill-line \general-align #Y #UP { \category-images \bookTitleMarkupCustom \null } } #})
#{ \markup { " " } #})
;; 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.
(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))
make-null-markup)
)))
\paper {
bookTitleMarkup = \markup \null
scoreTitleMarkup = \markup \null
oddHeaderMarkup = \markup { \if \on-first-page-of-part \title-with-category-images ##t }
evenHeaderMarkup = \markup { \if \on-first-page-of-part \title-with-category-images ##f }
oddHeaderMarkup = \markup { \if \on-first-page-of-part \build-full-title ##t }
evenHeaderMarkup = \markup { \if \on-first-page-of-part \build-full-title ##f }
oddTitleLineMarkup = \markup { \fill-line \general-align #Y #UP { \null \bookTitleMarkupCustom \category-images } }
evenTitleLineMarkup = \markup { \fill-line \general-align #Y #UP { \category-images \bookTitleMarkupCustom \null } }
defaultTitleMarkup = \markup {
\override #'(baseline-skip . 3.5)
\center-column {
+6 -1
View File
@@ -2,7 +2,12 @@ TRANSPOSITION = #(cons #f #f)
transposeGlobal =
#(define-void-function (from to) (ly:pitch? ly:pitch?)
(set! TRANSPOSITION (cons from to)))
(if (not (car TRANSPOSITION))
(set! TRANSPOSITION (cons from to))
(let ((current_to (cdr TRANSPOSITION))
(interval (ly:pitch-diff to from)))
(set! TRANSPOSITION (cons (car TRANSPOSITION)
(ly:pitch-transpose current_to interval))))))
transposable =
#(define-music-function (fromto music) (pair? ly:music?)
+118 -126
View File
@@ -60,19 +60,20 @@
% Text über Text mittig darstellen
#(define-markup-command (textup layout props text uptext) (markup? markup?)
#:properties ((verselayout generalLayout)
(verse-text-chord-distance songTextChordDistance))
"Markup über Text mittig darstellen."
(let ((verselayout (chain-assoc-get 'verselayout props generalLayout)))
(interpret-markup layout props
#{\markup {
\size-box-to-box-style-dependent ##t ##f
\general-align #X #LEFT \override #`(direction . ,UP) \override #'(baseline-skip . 1.0) \dir-column \chord-alignment-style-dependent {
\pad-to-box #'(0 . 0) #'(0 . 2.0) { #text }
\size-box-to-box ##f ##t #uptext \score { \chords { g4:m a } \layout { \verselayout } }
\general-align #X #LEFT \override #`(direction . ,UP) \override #'(baseline-skip . 1) \dir-column \chord-alignment-style-dependent {
\pad-to-box #'(0 . 0) #`(0 . ,(- verse-text-chord-distance 0.8)) { #text }
\size-box-to-box ##f ##t #uptext \score { \chords { g4:m a } \layout { $verselayout #(customized-layout verselayout) } }
}
#text
}
#}
)))
))
#(define-markup-command (anchor-x-between layout props arga argb)
(markup? markup?)
@@ -82,16 +83,15 @@
(ly:stencil-aligned-to m X (- (/ (* la 2) l) 1))
))
#(define-markup-command (stanza-raw layout props arg)
(string-or-music?)
(let ((verselayout (chain-assoc-get 'verselayout props generalLayout)))
#(define-markup-command (stanza-raw layout props arg) (string-or-music?)
#:properties ((verselayout generalLayout))
(interpret-markup layout props
(if (and (string? arg) (string-null? arg))
" "
#{\markup
\score { \new Lyrics { \lyricmode { #(if (ly:music? arg) arg #{ \set stanza = #arg #}) "" } } \layout { \verselayout } }
\score { \new Lyrics { \lyricmode { #(if (ly:music? arg) arg #{ \set stanza = #arg #}) "" } } \layout { $verselayout #(customized-layout verselayout) } }
#}
))))
)))
#(define-markup-command (stanza layout props arg)
(string-or-music?)
@@ -141,7 +141,7 @@
(make-wrap-newline-markup
(ly:regex-replace (ly:make-regex "\\(( *)([^,()]*)( *),([^)]*)\\)")
(ly:regex-replace (ly:make-regex "(([^ \n]*\\([^()]*,[^()]+\\)[^ \n(]*)+)") (handle-custom-newlines custom-verse-breaks verse) " \\concat { " 1 " } ")
"\\textup \\line { \"" 1 "\" " 2 " \"" 3 "\" } \\score { " transp " \\chords { s4 " 4 " } \\layout { \\verselayout } }")
"\\textup \\line { \"" 1 "\" " 2 " \"" 3 "\" } \\score { " transp " \\chords { s4 " 4 " } \\layout { $verselayout #(customized-layout verselayout) } }")
)
))))
@@ -182,7 +182,19 @@
((if reverses fold fold-right) (lambda (element filled-list)
(cons element (if (null? filled-list) '() (cons markup-between filled-list))))
'() elements))
(let* ((column-item-count (ceiling (/ (length versegroup) verse-cols)))
;; Dokumentposition fuer den ChordPro-/Data-Collector: jede Gruppe nimmt
;; sich eine Sequenznummer, jeder Vers traegt seine Position innerhalb
;; der Gruppe in den Props -- die Spalten-Verteilung unten verschraenkt
;; sonst die Interpretationsreihenfolge.
(set! chordpro-doc-seq-counter (1+ chordpro-doc-seq-counter))
(let* ((doc-seq chordpro-doc-seq-counter)
(versegroup (index-map
(lambda (index item)
(make-override-markup
(cons 'chordpro-doc-order (cons doc-seq index))
item))
versegroup))
(column-item-count (ceiling (/ (length versegroup) verse-cols)))
(column-data (make-list verse-cols)))
(let columnize-list ((index 0) (items versegroup))
(if (not (null? items))
@@ -203,23 +215,9 @@
#(define-markup-command (pad-left layout props amount arg)
(number? markup?)
(let* ((m (interpret-markup layout props arg))
(x (ly:stencil-extent m X))
(y (ly:stencil-extent m Y)))
(ly:stencil-translate
(ly:make-stencil (ly:stencil-expr m)
(cons (- (car x) amount) (cdr x))
y)
`(,amount . 0))))
#(define-markup-command (pad-right layout props amount arg)
(number? markup?)
(let* ((m (interpret-markup layout props arg))
(x (ly:stencil-extent m X))
(y (ly:stencil-extent m Y)))
(ly:make-stencil (ly:stencil-expr m)
(cons (car x) (+ (cdr x) amount))
y)))
(interpret-markup layout props (make-pad-x-left-markup amount arg))
`(,amount . 0)))
#(define-markup-command (score-equal-height-with-indents layout props lines)
(markup-list?)
@@ -250,115 +248,102 @@
(let ((text (ly:grob-property grob 'text)))
(grob-interpret-markup grob (if (string? text)
(make-pad-right-markup -0.1 (make-tied-lyric-markup text))
(make-pad-x-right-markup -0.1 (make-tied-lyric-markup text))
text))))
Chord_lyrics_spacing_engraver =
#(lambda (ctx)
(let ((last-note-head #f)
(note-head-extended #f)
(last-lyric-syllable-width 0)
(let ((last-lyric-syllable #f)
(lyric-width-since-last-chord 0)
(notes-on-syllable-count 0)
(last-chord-name #f)
(remaining-chord-width 0)
(last-rest #f)
(rest-count 0)
(multi-measure-rest-count 0)
(stanza-shift 0))
(music-columns-for-last-syllable 0)
(last-printed-chord #f)
(chord-width-since-last-lyric 0)
(lyrics-seen-since-break #f)
(have-a-rest #f)
(stanza #f)
(place-at-right-edge
(lambda (grob anchor padding)
(let ((anchor-width (interval-length (ly:grob-extent anchor anchor X))))
(ly:grob-set-parent! grob X anchor)
(ly:grob-set-property! grob 'X-offset (+ padding anchor-width))
))))
(make-engraver
(listeners
((multi-measure-rest-event engraver event)
(set! multi-measure-rest-count (+ multi-measure-rest-count 1))
(set! have-a-rest #t)
)
((rest-event engraver event)
(set! have-a-rest #t)
)
((lyric-event engraver event)
(set! have-a-rest #f)
(set! music-columns-for-last-syllable 0)
)
((break-event engraver event)
(set! last-note-head #f)
(set! note-head-extended #f)
(set! last-lyric-syllable-width 0)
(set! last-lyric-syllable #f)
(set! lyric-width-since-last-chord 0)
(set! notes-on-syllable-count 0)
(set! last-chord-name #f)
(set! remaining-chord-width 0)
(set! last-rest #f)
(set! rest-count 0)
(set! multi-measure-rest-count 0)
(set! stanza-shift 0)
(set! music-columns-for-last-syllable 0)
(set! last-printed-chord #f)
(set! chord-width-since-last-lyric 0)
(set! lyrics-seen-since-break #f)
))
(acknowledgers
((note-head-interface this-engraver grob source-engraver)
(if (and (> rest-count 0) (not last-note-head))
(let ((rest-spacing-on-line-start 1.2))
(ly:grob-set-property! grob 'minimum-X-extent (cons (- rest-spacing-on-line-start) 0))
(set! stanza-shift rest-spacing-on-line-start)
))
(set! notes-on-syllable-count (+ 1 notes-on-syllable-count))
(set! last-note-head grob)
(set! note-head-extended #f)
(set! last-rest #f)
(set! rest-count 0)
(set! multi-measure-rest-count 0)
((musical-paper-column-interface this-engraver grob source-engraver)
(set! music-columns-for-last-syllable (+ 1 music-columns-for-last-syllable))
)
((lyric-syllable-interface this-engraver grob source-engraver)
(set! remaining-chord-width (max 0 (- remaining-chord-width lyric-width-since-last-chord)))
(set! last-lyric-syllable-width (- (cdr (ly:grob-extent grob grob X)) 0.2))
(set! lyric-width-since-last-chord (+ lyric-width-since-last-chord last-lyric-syllable-width))
(if last-note-head (set! notes-on-syllable-count 1))
(let ((syllable-width (interval-length (ly:grob-extent grob grob X))))
(set! lyric-width-since-last-chord (+ lyric-width-since-last-chord syllable-width))
)
(if (> chord-width-since-last-lyric 0)
(if lyrics-seen-since-break
(ly:grob-set-property! grob 'extra-spacing-width
(cons (- chord-width-since-last-lyric) (cdr (ly:grob-property grob 'extra-spacing-width '(0 . 0)))))
(if last-printed-chord
(let ((gap-for-starting-rest 2.0))
(if stanza (ly:grob-set-property! stanza 'padding (+ 1 gap-for-starting-rest)))
(ly:grob-set-parent! grob X last-printed-chord)
(ly:grob-set-property! grob 'X-offset gap-for-starting-rest)
)))
)
(set! last-lyric-syllable grob)
(set! chord-width-since-last-lyric 0)
(set! lyrics-seen-since-break #t)
)
((chord-name-interface this-engraver grob source-engraver)
(if (not (and
(boolean? (ly:grob-property grob 'begin-of-line-visible))
(ly:grob-property grob 'begin-of-line-visible)))
(let ((on-a-rest (> rest-count 0)))
(if (not on-a-rest)
(set! notes-on-syllable-count (- notes-on-syllable-count 1)))
(if (and last-chord-name (= multi-measure-rest-count 1) (> lyric-width-since-last-chord remaining-chord-width))
(ly:grob-set-property! last-chord-name 'extra-spacing-width (cons -0.1 (+ 0.1 (- lyric-width-since-last-chord remaining-chord-width)))))
(if last-note-head
(let* ((last-note-min-x-extent (ly:grob-property last-note-head 'minimum-X-extent))
(last-note-min-x-lower (if (pair? last-note-min-x-extent) (car last-note-min-x-extent) 0))
(last-note-min-x-upper (if (pair? last-note-min-x-extent) (cdr last-note-min-x-extent) 0)))
(if on-a-rest
(begin
(if (not note-head-extended)
(begin
(ly:grob-set-property! last-note-head 'minimum-X-extent
(cons last-note-min-x-lower (- last-lyric-syllable-width -2 (* 2.2 rest-count))))
(set! note-head-extended #t)
(ly:grob-property grob 'begin-of-line-visible)
lyrics-seen-since-break))
(let* ((last-printed-chord-width (if last-printed-chord (interval-length (ly:grob-extent last-printed-chord last-printed-chord X)) 0))
(chord-overwidth (- last-printed-chord-width lyric-width-since-last-chord))
(chord-gap 0.5))
(if have-a-rest
(let ((chord-width (interval-length (ly:grob-extent grob grob X))))
(if last-lyric-syllable
(if (and last-printed-chord (> chord-overwidth 0))
(place-at-right-edge grob last-printed-chord chord-gap)
(place-at-right-edge grob last-lyric-syllable 0))
(if last-printed-chord
(place-at-right-edge grob last-printed-chord chord-gap)))
(set! chord-width-since-last-lyric (+ chord-width-since-last-lyric chord-width chord-gap))
))
(ly:grob-set-property! last-rest 'minimum-X-extent (cons 0 2))
(if (and last-lyric-syllable last-printed-chord (> chord-overwidth 0))
(ly:grob-set-property! last-lyric-syllable 'extra-spacing-width
(cons (car (ly:grob-property last-lyric-syllable 'extra-spacing-width '(0 . 0))) (+ chord-gap chord-overwidth)))
)
(if (and (> lyric-width-since-last-chord 0)
(> remaining-chord-width lyric-width-since-last-chord))
(ly:grob-set-property! last-note-head 'minimum-X-extent
(cons (- -1.2 (- remaining-chord-width lyric-width-since-last-chord)) last-note-min-x-upper))
(let* ((width-per-note-head 0.5)
(note-width-since-last-chord (* width-per-note-head notes-on-syllable-count)))
(if (> remaining-chord-width note-width-since-last-chord)
(ly:grob-set-property! last-note-head 'minimum-X-extent
(cons (- note-width-since-last-chord remaining-chord-width) last-note-min-x-upper))
(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! last-printed-chord grob)
(set! last-lyric-syllable #f)
)
(ly:grob-set-property! grob 'X-extent '(+inf.0 . -inf.0))
)
)
((stanza-number-interface this-engraver grob source-engraver)
(set! stanza grob)
)
)
)))
(set! last-chord-name grob)
(set! remaining-chord-width
(if (and on-a-rest (equal? (ly:prob-property (ly:grob-property grob 'cause) 'duration) (ly:prob-property (ly:grob-property last-rest 'cause) 'duration)))
0
(cdr (ly:grob-extent grob grob X))))
(set! lyric-width-since-last-chord 0)
(set! notes-on-syllable-count (if on-a-rest 0 1))
))
)
((rest-interface this-engraver grob source-engraver)
(set! rest-count (+ 1 rest-count))
(set! last-rest grob)
(set! multi-measure-rest-count 0)
)
((stanza-number-interface this-engraver grob source-engraver)
(ly:grob-set-property! grob 'padding (+ 1 stanza-shift)))
))))
%#(ly:set-option 'debug-skylines #t)
#(define-markup-command (chordlyrics layout props lyrics) (ly:music?)
#:properties ((verse-chords #{#})
@@ -368,6 +353,20 @@ Chord_lyrics_spacing_engraver =
(transposition (cons #f #f))
(verselayout generalLayout))
"Vers mit Akkorden"
;; Dem ChordPro-Collector sagen, zu welchem Lied dieser Vers gehoert
;; (Buch: songfilename aus den Props via \setsongfilename; Einzellied:
;; Fallback-Key aus layout_bottom). Die Score-Interpretation darunter
;; laeuft synchron, der Engraver liest den Key in initialize. Die Props
;; braucht er zum Aufloesen von Markup-Silben (tags-to-keep).
(set! chordpro-active-song-key (chordpro-resolve-song-key props))
(set! chordpro-active-props props)
;; Dokumentposition: von \group-verses gestempelt; ausserhalb einer
;; Gruppe nimmt sich der Vers selbst eine Sequenznummer.
(set! chordpro-active-doc-order
(or (chain-assoc-get 'chordpro-doc-order props #f)
(begin
(set! chordpro-doc-seq-counter (1+ chordpro-doc-seq-counter))
(cons chordpro-doc-seq-counter 0))))
(interpret-markup layout props
#{
\markup {
@@ -379,14 +378,13 @@ Chord_lyrics_spacing_engraver =
\new Lyrics \lyricsto "dummyvoice" { #lyrics }
>>
\layout {
\verselayout
#(let
((custom-size (ly:output-def-lookup verselayout 'size #f)))
(if custom-size (layout-set-staff-size custom-size)))
$verselayout
#(customized-layout verselayout)
ragged-right = ##t
\context {
\Lyrics
\override VerticalAxisGroup.nonstaff-relatedstaff-spacing.basic-distance = #verse-text-chord-distance
\override VerticalAxisGroup.nonstaff-relatedstaff-spacing.padding = #(- verse-text-chord-distance songTextChordDistance)
\override LyricText.parent-alignment-X = #LEFT
\override LyricText.self-alignment-X = #LEFT
\override LyricText.word-space = 0.8
@@ -405,14 +403,17 @@ Chord_lyrics_spacing_engraver =
% \override SpacingSpanner.strict-note-spacing = ##t
\override SpacingSpanner.uniform-stretching = ##t
\override SpacingSpanner.spacing-increment = 0
%\override SpacingSpanner.packed-spacing = ##t
% \override SpacingSpanner.packed-spacing = ##t
\consists \Chord_lyrics_spacing_engraver
% ChordPro engraver in Score context to collect all data
\consists \ChordPro_score_collector
\remove Bar_number_engraver
\remove Mark_engraver
\remove Jump_engraver
\remove Volta_engraver
\remove Parenthesis_engraver
\remove Metronome_mark_engraver
\remove Text_mark_engraver
}
\context {
\Staff
@@ -436,15 +437,6 @@ Chord_lyrics_spacing_engraver =
\remove Note_heads_engraver
\remove Script_engraver
}
\context {
\NullVoice
\consists Rest_engraver
\omit Rest
\override Rest.X-extent = #'(0 . 0)
\undo \omit NoteHead
\hide NoteHead
\override NoteHead.X-extent = #'(0 . 0.5)
}
}
}
}
+296 -29
View File
@@ -65,7 +65,30 @@ additionalPageNumbers =
display-pages-list
)
% Fuer Zusatzseiten: Label der ersten Seite des zusammenhaengenden
% Zusatzseiten-Blocks, zu dem label gehoert (bestimmt den Buchstaben-Suffix a, b, ...).
#(define (earliest-additional-label label)
(let find-earliest-additional-label
((rest-additional-page-switch-label-list (member (cons label #t) additional-page-switch-label-list)))
(if (cdadr rest-additional-page-switch-label-list)
(find-earliest-additional-label (cdr rest-additional-page-switch-label-list))
(caar rest-additional-page-switch-label-list))))
% Sichtbare Seitenzahl der Seite mit diesem Label: normalerweise eine Zahl,
% auf Zusatzseiten ein String mit Buchstaben-Suffix (z.B. "12a"); gleiche
% Logik wie custom-page-number unten.
#(define (visible-page-value layout label)
(let ((display-page (assq-ref (build-display-pages-list layout) label)))
(if (assq-ref additional-page-switch-label-list label)
(ly:format "~a~a" display-page
(string (integer->char (+ 97 (- (real-page-number layout label)
(real-page-number layout (earliest-additional-label label)))))))
display-page)))
% TODO:
% Eigentlich können wir das direkt in oddFooderMarkup und evenFooterMarkup aufrufen
% vermutlich sogar ohne den delay kram. Wir sollten außerdem einfach nur die property
% page:page-number-string setzen dann klappts auch mit PDF Seiten
#(define-markup-command (custom-page-number layout props label real-current-page-number)
(symbol? number?)
#:category other
@@ -89,17 +112,13 @@ width may require additional tweaking.)"
,(delay (ly:stencil-expr
(let* ((display-page (assq-ref (build-display-pages-list layout) label))
(real-current-page (if (negative? real-current-page-number) (real-page-number layout label) real-current-page-number))
(number-type (ly:output-def-lookup layout 'page-number-type))
(page-markup
(if (assq-ref additional-page-switch-label-list label)
(make-concat-markup (list (number-format 'arabic display-page)
(make-concat-markup (list (number-format number-type display-page)
(make-char-markup (+ 97 (- real-current-page (real-page-number layout
(let find-earliest-additional-label
((rest-additional-page-switch-label-list (member (cons label #t) additional-page-switch-label-list)))
(if (cdadr rest-additional-page-switch-label-list)
(find-earliest-additional-label (cdr rest-additional-page-switch-label-list))
(caar rest-additional-page-switch-label-list)))
))))))
(number-format 'arabic (+ display-page (- real-current-page (real-page-number layout label))))
(earliest-additional-label label)))))))
(number-format number-type (+ display-page (- real-current-page (real-page-number layout label))))
))
(page-stencil (interpret-markup layout props page-markup))
(gap (- (interval-length x-ext)
@@ -114,44 +133,141 @@ width may require additional tweaking.)"
(make-filled-box-stencil x-ext y-ext))))
%% Schreibt die Daten aller Lieder des Buchs (extract-song-data, siehe
%% data_extractor.ily) als YAML-Datei: oberster Key contents, darunter eine
%% Liste mit den Lied-Daten, jeweils angereichert um den Key page mit der
%% sichtbaren Seitenzahl des Liedanfangs. Einbinden wie \write-toc-csv in
%% einem Markup des Buchs; wie dort passiert das Schreiben ueber ein
%% delayed stencil, weil die Label->Seiten-Tabelle erst beim Ausgeben des
%% Buchs feststeht. Der Dateiname ist der Ausgabename des Buchs
%% (\bookOutputName, sonst der .ly-Basisname) mit angehaengtem .yml --
%% landet also wie das PDF relativ zum Arbeitsverzeichnis.
#(define-markup-command (write-book-yaml layout props) ()
(let ((filename (string-append (ly:parser-output-name) ".yml")))
(ly:make-stencil
`(delay-stencil-evaluation
,(delay
(begin
(scm->yml-file filename
(list (cons 'contents
(map (lambda (song)
(let ((songvars (cdr song)))
(cons (cons 'page (visible-page-value layout (assq-ref songvars 'label)))
(extract-song-data (assq-ref songvars 'header)
(assq-ref songvars 'music)
;; Textseiten-Strophen aus dem Render-
;; Collector-Store (Key = Liedname); beim
;; Ausgeben des Buchs sind alle Textseiten
;; bereits interpretiert.
(hash-ref chordpro-song-store
(symbol->string (car song)))
(symbol->string (car song))))))
(reverse (alist-delete 'markupPage
(alist-delete 'imagePage
(alist-delete 'emptyPage song-list))))))))
empty-stencil))))))
%% Schreibt den ChordPro-Export aller Lieder des Buchs (in Dokument-
%% reihenfolge) als EINE Datei statt als Einzeldateien pro Lied. Lieder
%% werden mit der ChordPro-Direktive {new_song} getrennt; vor dem ersten
%% Lied ist sie nicht noetig, sie gilt am Dateianfang implizit (siehe
%% https://www.chordpro.org/chordpro/directives-new_song/). Einbinden wie
%% \write-book-yaml an beliebiger Stelle im Buch -- unabhaengig von
%% chordpro-export-enabled (das steuert nur den Pro-Song-Export), beide
%% Befehle koennen auch gleichzeitig genutzt werden. Wie bei
%% \write-book-yaml passiert das Schreiben ueber ein delayed stencil,
%% weil der Render-Collector-Store erst beim Ausgeben des Buchs
%% vollstaendig gefuellt ist. Der Dateiname ist wie bei \write-book-yaml
%% der Ausgabename des Buchs mit angehaengtem .cho.
#(define-markup-command (write-book-chordpro layout props) ()
(let ((filename (string-append (ly:parser-output-name) ".cho")))
(ly:make-stencil
`(delay-stencil-evaluation
,(delay
(begin
(with-output-to-file filename
(lambda ()
(let loop ((songs (filter
;; \chordlyrics-Strophen ODER (bei
;; chordpro-include-music-lyrics) aus der
;; Musik nachgeholte Kandidaten.
(lambda (song) (or (> (chordpro-song-verse-index song) 0)
(pair? (chordpro-song-music-candidates song))))
(filter-map
(lambda (song) (hash-ref chordpro-song-store
(symbol->string (car song))))
(reverse (alist-delete 'markupPage
(alist-delete 'imagePage
(alist-delete 'emptyPage song-list)))))))
(first? #t))
(when (pair? songs)
(unless first? (display "{new_song}\n"))
(display (chordpro-format-song (car songs)))
(loop (cdr songs) #f)))))
empty-stencil))))))
includeSong =
#(define-void-function (parser location filename) (string?)
#(define-void-function (filename) (string?)
#{
\bookOutputName #filename
#}
(ly:parser-parse-string (ly:parser-clone)
(ly:format "\\include \"~a/~a/~a.ly\"" songPath filename filename))
;; ChordPro: Titel/Autoren dieses Lieds im Render-Collector-Store
;; registrieren -- basicSongInfo haelt direkt nach dem Include genau
;; dieses Lied. Der Key ist der Liedname; die Collector-Daten (Verse,
;; Akkorde) kommen spaeter beim Rendern ueber \setsongfilename unter
;; demselben Key dazu. Unabhaengig von chordpro-export-enabled, damit
;; sowohl der Pro-Song-Export (der die Flag braucht) als auch
;; \write-book-chordpro (das nur explizit aufgerufen werden muss) auf
;; vollstaendige Metadaten zugreifen koennen.
(chordpro-register-song-metadata filename HEADER)
(let ((label (gensym "index")))
(set! additional-page-switch-label-list
(acons label additional-page-numbers additional-page-switch-label-list))
(set! song-list
(acons (string->symbol filename)
(acons 'label label
(acons 'header HEADER
(acons 'music MUSIC
(acons 'layout LAYOUT
(acons 'text-pages
(map (lambda (text)
`(
(label . ,label)
(header . ,HEADER)
(music . ,MUSIC)
(layout . ,LAYOUT)
(text-pages . ,(map (lambda (text)
#{ \markuplist \setsongfilename $filename $text #})
TEXT_PAGES)
'())))))
TEXT_PAGES))
)
song-list))
))
blankpage =
#(define-void-function (parser location) ()
#(define-void-function () ()
(set! song-list
(acons 'emptyPage
'()
song-list)))
imagepage =
#(define-void-function (parser location xsize filename) (number? string?)
#(define-void-function (xsize filename) (number? string?)
(set! song-list
(acons 'imagePage
(acons 'xsize xsize (acons 'filename filename '()))
`((xsize . ,xsize)
(filename . ,filename))
song-list)))
markuppage =
#(define-void-function (mkup) (markup?)
(set! song-list
(acons 'markupPage
`((markup . ,mkup))
song-list)))
#(define chapters '())
songsChapter =
#(define-void-function (title) (string?)
(set! chapters
(cons (cons title (length song-list)) chapters)))
#(define-markup-command (pagecenter layout props stuff)(markup?)
(interpret-markup layout props
(let ((halfpaperheight (/ (ly:output-def-lookup layout 'paper-height) 2))
@@ -160,16 +276,86 @@ imagepage =
)))
songs =
#(define-void-function (parser location) ()
#(define-void-function () ()
(let* ((ordered (reverse song-list))
(total (length ordered))
;; Ein Lied erzeugt nur dann einen PDF-Outline-Eintrag (/OUT), wenn
;; title oder starttext gesetzt sind (siehe build-full-title). Nur solche
;; 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?
(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)))))
(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)))))))
;; chapters: (title . start-index). start-index = Anzahl der song-list-
;; Eintraege beim Aufruf von songsChapter = Index in `ordered`, ab dem
;; das Kapitel beginnt.
(chapters-asc (sort chapters (lambda (a b) (< (cdr a) (cdr b)))))
;; pending: aufsteigende Liste von (start title count); count = Anzahl
;; der Lesezeichen-Lieder von diesem Kapitel bis zum Beginn des naechsten.
(pending
(let loop ((chs chapters-asc) (acc '()))
(if (null? chs)
(reverse acc)
(let* ((title (caar chs))
(start (cdar chs))
(end (if (null? (cdr chs)) total (cdadr chs)))
(cnt (count-bookmark-songs start end)))
(loop (cdr chs) (cons (list start title cnt) acc))))))
;; Aktuell aktives Kapitel (Titel-String) waehrend des Durchlaufs. Wird
;; gesetzt, sobald ein Kapitel aktiviert (aus `pending` entfernt) wird --
;; egal ob auf einer Seite (chapter-prefixed-markup) oder auf dem ersten
;; Song. Jeder danach folgende Song erhaelt dieses Kapitel als
;; chapterref-Header-Feld, woraus toc_include.ily den Kapitel-Index baut.
(current-chapter #f)
;; Stellt einer beliebigen Seite (markup-/image-/emptyPage) den Kapitel-
;; Eintrag (chapter-to-pdf-toc) bei, falls an Position `this-index` ein
;; Kapitel faellig ist, und entfernt es aus `pending`. So fuehrt die
;; Kapitel-Referenz zur ersten Seite nach dem \songsChapter -- egal
;; welcher Seitentyp das ist. Der Eintrag wird als unsichtbares Overlay
;; (\combine) beigelegt, damit er das Seitenlayout nicht veraendert;
;; \with-dimensions gibt dem sonst extent-losen pdfmark einen minimalen
;; Extent, damit er auch auf leeren Seiten nicht verworfen wird.
(chapter-prefixed-markup
(lambda (this-index base-markup)
(if (and (pair? pending) (<= (car (car pending)) this-index))
(let ((cnt (caddr (car pending)))
(title (cadr (car pending))))
(set! pending (cdr pending))
(set! current-chapter title)
(make-combine-markup
base-markup
(make-with-dimensions-markup '(0 . 0) '(0 . 0.1)
(make-chapter-to-pdf-toc-markup cnt title))))
base-markup)))
(index 0))
(for-each (lambda (songitems)
(let ((this-index index))
(set! index (+ index 1))
(ly:book-add-bookpart! (ly:parser-lookup '$current-book)
(let ((filename (car songitems))
(songvars (cdr songitems)))
(if (eq? filename 'emptyPage)
#{ \bookpart { \markup { \null } } #}
#{ \bookpart { \markup { $(chapter-prefixed-markup this-index (make-null-markup)) } } #}
(if (eq? filename 'markupPage)
;; Kapitelanfang auf einer markuppage: Kapitel-Eintrag wird dieser
;; Seite beigelegt (siehe chapter-prefixed-markup).
#{ \bookpart { \markup { $(chapter-prefixed-markup this-index (assq-ref songvars 'markup)) } } #}
(if (eq? filename 'imagePage)
(let ((xsize (assq-ref songvars 'xsize))
(filename (ly:format "~a/~a" imagePagePath (assq-ref songvars 'filename))))
(let* ((xsize (assq-ref songvars 'xsize))
(filename (ly:format "~a/~a" imagePagePath (assq-ref songvars 'filename)))
(img-markup #{ \markup { \pagecenter { \epsfile #X #xsize #filename } } #}))
#{ \bookpart {
\paper {
%{
@@ -183,30 +369,111 @@ songs =
last-bottom-spacing = #'((basic-distance . 0) (minimum-distance . 0) (padding . 0))
page-count = 1
}
\markup { \pagecenter { \epsfile #X #xsize #filename } }
\markup { $(chapter-prefixed-markup this-index img-markup) }
} #}
)
;; Ist ein Kapitel faellig (Start <= aktuelle Position) und dies ein
;; echtes Lied, wird die Kapitelinfo an dieses Lied gehaengt und aus
;; `pending` entfernt.
(let* ((newnumber (+ 1 song-number))
(chaptertitle (if (and (pair? pending) (<= (car (car pending)) this-index))
(cadr (car pending)) #f))
(chaptercount (if chaptertitle (caddr (car pending)) 0))
;; Startet das Kapitel erst auf diesem Song (nicht schon auf
;; einer vorangehenden Seite), wird current-chapter jetzt
;; gesetzt und pending entfernt -- vor dem Bau des Headers,
;; damit chapterref bereits das neue Kapitel enthaelt.
(_ (if chaptertitle
(begin (set! current-chapter chaptertitle)
(set! pending (cdr pending)))))
(header #{ \bookpart { $(assq-ref songvars 'header) \header {
songfilename = $(symbol->string filename)
myindexlabel = #(assq-ref songvars 'label)
songnumber = #(number->string newnumber)
chaptertitle = #chaptertitle
chaptercount = #chaptercount
chapterref = #current-chapter
} } #})
(music (assq-ref songvars 'music))
(layout (assq-ref songvars 'layout))
(text-pages (assq-ref songvars 'text-pages))
(label (assq-ref songvars 'label)))
(set! song-number newnumber)
;; ChordPro_music_collector (Strophen unter den Noten, siehe
;; chordpro.ily) braucht den Song-Key beim tatsaechlichen
;; Interpretieren dieses Songs' Hauptscore. Im Buch ist DAS
;; NICHT synchron zu dieser for-each-Schleife -- LilyPond
;; interpretiert die \score-Bloecke aller Lieder gesammelt fuer
;; das Seitenlayout, oft NACHDEM die Schleife schon beim
;; naechsten (oder letzten) Lied angelangt ist. Ein globaler
;; chordpro-active-song-key waere zu diesem Zeitpunkt also auf
;; ein ANDERES Lied gesetzt -- die gesammelten Strophen landen
;; dann fälschlich im Store-Eintrag des zuletzt verarbeiteten
;; Lieds (beobachtet: mehrere Lieder ohne eigene \chordlyrics
;; wurden alle in den letzten Song der Test-Bibliothek
;; geschrieben). Deshalb setzen wir den Key per \applyContext
;; DIREKT IN DER MUSIK in chordpro-context-song-keys (siehe
;; chordpro.ily) -- das laeuft synchron zur tatsaechlichen
;; Interpretation dieses Scores und ist deshalb robust gegen die
;; verzoegerte/gesammelte Verarbeitung (eine LilyPond-Context-
;; Property ueber den \layout-Block waere hier keine Alternative:
;; LilyPond verwirft \applyContext-\ly:context-set-property!-
;; Aufrufe fuer nicht deklarierte Properties stillschweigend).
;; Der Music_collector liest bevorzugt diese Tabelle,
;; chordpro-active-song-key bleibt nur als Fallback
;; (Einzellied-Standalone-Pfad, dort synchron genug).
;; \consists nur bei chordpro-include-music-lyrics -- sonst
;; keine Kosten (Grob-Acknowledger fuer JEDEN Akkord/jede
;; Silbe/Strophenmarker des Hauptscores) fuer Buecher, die das
;; Feature nicht nutzen. Bewusst NICHT zusaetzlich an
;; chordpro-export-enabled gekoppelt -- die steuert nur, ob
;; \write-book-chordpro/\chordpro-delayed-write am Ende etwas
;; schreiben, nicht ob hier gesammelt wird.
(set! chordpro-active-song-key (symbol->string filename))
(let* ((collect-music-lyrics? (and (defined? 'chordpro-include-music-lyrics) chordpro-include-music-lyrics))
(song-layout
(if collect-music-lyrics?
#{
\layout {
$layout
songfilename = $(symbol->string filename)
\context {
\Score
\consists \ChordPro_music_collector
}
}
#}
#{
\layout {
$layout
songfilename = $(symbol->string filename)
}
#}))
(score-music
(if collect-music-lyrics?
#{
<<
\applyContext
#(let ((key (symbol->string filename)))
(lambda (ctx)
(hashq-set! chordpro-context-song-keys ctx key)))
$music
>>
#}
music)))
#{
\bookpart {
$header
\headerToTOC #header #label
\score { $music \layout { $layout } }
\score {
$score-music
$song-layout
}
$(add-text-pages text-pages)
}
#}))))))
(reverse song-list)
))
#})))))))))
ordered
)))
#(define (boernel-stats)
(let (
+84 -39
View File
@@ -56,25 +56,14 @@
#(let ((index-item-list (list)))
(set! add-index-item!
(lambda* (markup-symbol text sorttext #:optional (label (gensym "index")))
(lambda* (markup-symbol textoptions sorttext #:optional (label (gensym "index")))
(set! index-item-list
;; We insert index items sorted from the beginning on and do
;; not sort them later - this saves pretty much computing time
(insert-alphabetical-sorted! (list label markup-symbol text
;; this crazy hack is necessary because lilypond depends on guile 1.8 atm
;; and so the cool unicode conversion functions cannot be used
(insert-alphabetical-sorted! (list label markup-symbol textoptions
(ly:string-substitute " " ""
(ly:string-substitute "" ""
(ly:string-substitute "Č" "C"
(ly:string-substitute "Đ" "D"
(ly:string-substitute "Š" "S"
(ly:string-substitute "Т" "T"
(ly:string-substitute "Ä" "Ae"
(ly:string-substitute "ä" "ae"
(ly:string-substitute "Ö" "O"
(ly:string-substitute "ö" "oe"
(ly:string-substitute "Ü" "U"
(ly:string-substitute "ü" "ue" sorttext)))))))))))))
(ly:string-substitute "." ""
(transliterate-de sorttext))))
index-item-list))
(make-music 'EventChord
'page-marker #t
@@ -92,20 +81,20 @@
(cons (car ilist) (insert-alphabetical-sorted! iitem (cdr ilist))))))
% code for category index
#(define*-public (add-category-index-item! categories markup-symbol text #:optional label) #f)
#(define*-public (add-category-index-item! categories markup-symbol textoptions #:optional label) #f)
#(define-public (category-index-items) #f)
#(let ((category-index-hash (make-hash-table)))
(set! add-category-index-item!
(lambda* (categories markup-symbol text #:optional (label (gensym "index")))
(lambda* (categories markup-symbol textoptions #:optional (label (gensym "index")))
(for-each (lambda (category)
(let* ((catsym (string->symbol category))
(catlist (hashq-ref category-index-hash catsym
(list (list label 'indexCategoryMarkup category)))))
(list (list label 'indexCategoryMarkup `(((combine-with-next . #t) (rawtext . ,category))))))))
(if (assq catsym category-names)
(hashq-set! category-index-hash catsym
(cons (list label markup-symbol text) catlist))
(ly:error "song: <~a> category ~a is not defined!" (markup->string text) category))))
(cons (list label markup-symbol textoptions) catlist))
(ly:error "song: <~a> category ~a is not defined!" (markup->string (chain-assoc-get 'rawtext textoptions)) category))))
categories)
(make-music 'EventChord
'page-marker #t
@@ -121,13 +110,13 @@
#(let ((author-index-hash (make-hash-table)))
(set! add-author-index-item!
(lambda* (authorIDs markup-symbol text #:optional (label (gensym "index")))
(lambda* (authorIDs markup-symbol textoptions #:optional (label (gensym "index")))
(for-each (lambda (authorID)
(let* ((authorsym (string->symbol authorID))
(authorlist (hashq-ref author-index-hash authorsym
(list (list label 'indexAuthorMarkup authorID)))))
(list (list label 'indexAuthorMarkup `(((combine-with-next . #t) (rawtext . ,authorID))))))))
(hashq-set! author-index-hash authorsym
(cons (list label markup-symbol text) authorlist))
(cons (list label markup-symbol textoptions) authorlist))
))
authorIDs)
(make-music 'EventChord
@@ -143,6 +132,37 @@
author-index-hash)
(lambda (a b) (string-ci<? (car a) (car b))))))))
% code for chapter index
% Anders als bei Kategorien/Autoren ergibt sich die Kapitelzugehoerigkeit nicht
% aus dem Song-Header, sondern aus dem \songsChapter, das dem Song vorangeht
% (in book_include.ily als Header-Feld chapterref an jeden Song gehaengt).
% Die Kapitel behalten hier ihre Auftritts-Reihenfolge (nicht alphabetisch),
% ebenso die Songs innerhalb eines Kapitels ihre Song-Reihenfolge.
#(define*-public (add-chapter-index-item! chapter markup-symbol textoptions #:optional label) #f)
#(define-public (chapter-index-items) #f)
#(let ((chapter-index-hash (make-hash-table))
(chapter-order '()))
(set! add-chapter-index-item!
(lambda* (chapter markup-symbol textoptions #:optional (label (gensym "index")))
(let* ((chaptersym (string->symbol chapter))
(existing (hashq-ref chapter-index-hash chaptersym #f))
(chapterlist (or existing
(begin
(set! chapter-order (cons chaptersym chapter-order))
(list (list label 'indexChapterMarkup `(((combine-with-next . #t) (rawtext . ,chapter))))))))
)
(hashq-set! chapter-index-hash chaptersym
(cons (list label markup-symbol textoptions) chapterlist)))
(make-music 'EventChord
'page-marker #t
'page-label label
'elements (list (make-music 'LabelEvent
'page-label label)))))
(set! chapter-index-items (lambda ()
(append-map (lambda (chaptersym) (reverse (hashq-ref chapter-index-hash chaptersym (list))))
(reverse chapter-order)))))
#(define-markup-command (with-link-symbol-ref layout props symbol arg)
(symbol? markup?)
@@ -226,10 +246,15 @@
(make-null-markup))
\vspace #.4
}
indexChapterMarkup = \markup \override #'(baseline-skip . 1.5) \left-column {
\vspace #1
\sans \bold \fontsize #3 \fromproperty #'index:text
\vspace #.4
}
}
#(define (prepare-item-markup items layout)
(map (lambda (index-item)
(define (single-item-markup index-item)
(let* ((label (car index-item))
(index-markup (cadr index-item))
(textoptions (caddr index-item))
@@ -242,21 +267,31 @@
#:override (cons 'index:alternative alternative)
#:override (cons 'index:songnumber songnumber)
(ly:output-def-lookup layout index-markup))))
(items)))
(if (null? items)
items
(let* ((index-item (car items))
(combine-with-next (chain-assoc-get 'combine-with-next (caddr index-item) #f))
(restitems (cdr items))
(item-markup (single-item-markup index-item)))
(if (and combine-with-next (not (null? restitems)))
(cons (make-column-markup (list item-markup (single-item-markup (car restitems)))) (prepare-item-markup (cdr restitems) layout))
(cons item-markup (prepare-item-markup restitems layout))))
))
#(define-markup-list-command (index-in-columns-with-title layout props index-type title-markup) (symbol? markup?)
( _i "Outputs index alphabetical sorted or in categories" )
(let ((items (case index-type
((alphabetical) index-items)
((categories) category-index-items)
((authors) author-index-items)))
((authors) author-index-items)
((chapters) chapter-index-items)))
(title (interpret-markup layout props title-markup)))
(cons title
(interpret-markup-list layout props
(make-columnlayout-markup-list songTocColumns 2
(let ((h (- (ly:output-def-lookup layout 'paper-height) 12)))
(cons (- h (interval-length (ly:stencil-extent title Y))) h))
(prepare-item-markup items layout))))))
(prepare-item-markup (items) layout))))))
indexItem =
#(define-music-function (parser location sorttext text) (string? markup?)
@@ -266,7 +301,7 @@ indexItem =
indexSection =
#(define-music-function (parser location sorttext text) (string? markup?)
"Add a section line to the alphabetical index, using @code{indexSectionMarkup} paper variable markup. This can be used to divide the alphabetical index into different sections, for example one section for each first letter."
(add-index-item! 'indexSectionMarkup (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)
(let* ((headervars (hash-map->list cons (struct-ref (ly:book-header bookheader) 0)))
@@ -285,17 +320,18 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(versePoetData (find-author-id-with-part-numbers 'verse authors))
(composerIds (find-author-ids-by 'melody authors))
(verseComposerData (find-author-id-with-part-numbers 'meloverse authors))
(refComposerIds (find-author-ids-by 'meloref authors))
(voiceComposerData (find-author-id-with-part-numbers 'voice authors))
(compositionIds (find-author-ids-by 'composition authors))
(adaptionIds (find-author-ids-by 'adaption authors))
(bridgeIds (find-author-ids-by 'bridge authors))
(interludeIds (find-author-ids-by 'interlude authors)))
(delete-duplicates
(append poetIds translatorIds (map car versePoetData) composerIds (map car verseComposerData) (map car voiceComposerData) compositionIds adaptionIds bridgeIds interludeIds))
(append poetIds translatorIds (map car versePoetData) composerIds (map car verseComposerData) refComposerIds (map car voiceComposerData) compositionIds adaptionIds bridgeIds interludeIds))
))
(let*
(
(extractedheadervars (extract-and-check-vars-from-header header '(title starttext alttitle categorytitle categories authors songnumber)))
(extractedheadervars (extract-and-check-vars-from-header header '(title starttext alttitle categorytitle categories authors songnumber chapterref)))
(title (assq-ref extractedheadervars 'title))
(starttext (assq-ref extractedheadervars 'starttext))
(alttitle (assq-ref extractedheadervars 'alttitle))
@@ -303,12 +339,14 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(categories (assq-ref extractedheadervars 'categories))
(authors (assq-ref extractedheadervars 'authors))
(songnumber (assq-ref extractedheadervars 'songnumber))
(chapterref (assq-ref extractedheadervars 'chapterref))
(textoptions (lambda (text alternative) `(((rawtext . ,text) (alternative . ,alternative) (songnumber . ,songnumber)))))
(add-to-toc! (lambda (toctitle alternative)
(add-index-item! 'indexItemMarkup (textoptions toctitle alternative) toctitle label)))
)
(if categories (add-category-index-item! (string-tokenize categories) 'indexItemMarkup (textoptions (if categorytitle categorytitle title) #f) label))
(if authors (add-author-index-item! (all-author-ids authors) 'indexItemMarkup (textoptions (if categorytitle categorytitle title) #f) label))
(if chapterref (add-chapter-index-item! chapterref 'indexItemMarkup (textoptions (if categorytitle categorytitle title) #f) label))
(if starttext (add-to-toc! starttext #t))
(if alttitle
(if (list? alttitle)
@@ -332,6 +370,9 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
#(define csv-write sxml->csv)
#(define-markup-command (write-toc-csv layout props) ()
;; Dateiname wie bei \write-book-yaml/\write-book-chordpro: der
;; Ausgabename des Buchs mit angehaengtem .csv statt fest "toc.csv".
(define csv-filename (string-append (ly:parser-output-name) ".csv"))
(define (csv-escape field)
(if (string-null? field)
field
@@ -367,7 +408,7 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(let ((song-lines (map (lambda (song)
(let* ((filename (symbol->string (car song)))
(songvars (cdr song))
(page-number (number->string (assoc-get (assq-ref songvars 'label) labelPageTable)))
(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)))
(headervar-or-empty (lambda (varsym)
@@ -379,9 +420,11 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(versePoetData (find-author-id-with-part-numbers 'verse authors))
(composerIds (find-author-ids-by 'melody authors))
(verseComposerData (find-author-id-with-part-numbers 'meloverse authors))
(refComposerIds (find-author-ids-by 'meloref authors))
(voiceComposerData (find-author-id-with-part-numbers 'voice authors))
(compositionIds (find-author-ids-by 'composition authors))
(adaptionIds (find-author-ids-by 'adaption authors))
(adaptionTextIds (find-author-ids-by 'adaption_text authors))
(adaptionMusicIds (find-author-ids-by 'adaption_music authors))
(bridgeIds (find-author-ids-by 'bridge authors))
(interludeIds (find-author-ids-by 'interlude authors)))
(map csv-escape
@@ -397,22 +440,23 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
(headervar-or-empty 'categorytitle)
(headervar-or-empty 'categories)
(format-authors (append poetIds (map car versePoetData)))
(format-authors (append poetIds adaptionTextIds (map car versePoetData)))
(format-authors translatorIds)
(format-authors (append composerIds compositionIds adaptionIds bridgeIds interludeIds (map car voiceComposerData) (map car verseComposerData)))
(format-authors (append composerIds compositionIds adaptionMusicIds bridgeIds interludeIds (map car voiceComposerData) (map car verseComposerData) refComposerIds))
(headervar-or-empty 'year_text)
(headervar-or-empty 'year_melody)
(headervar-or-empty 'year_translation)
(headervar-or-empty 'year_composition)
(headervar-or-empty 'year_adaption)
(headervar-or-empty 'year_adaption_text)
(headervar-or-empty 'year_adaption_music)
(headervar-or-empty 'copyright)
(headervar-or-empty 'source)
(format-info-paragraphs (headervar-or-empty 'infotext))
(format-info-paragraphs (headervar-or-empty 'translation))
(format-info-paragraphs (headervar-or-empty 'pronunciation))
))))
(alist-delete 'imagePage (alist-delete 'emptyPage song-list)))))
(call-with-output-file "toc.csv"
(alist-delete 'markupPage (alist-delete 'imagePage (alist-delete 'emptyPage song-list))))))
(call-with-output-file csv-filename
(lambda (port)
(csv-write (cons '(
"filename"
@@ -429,7 +473,8 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
"year_melody"
"year_translation"
"year_composition"
"year_adaption"
"year_adaption_text"
"year_adaption_music"
"copyright"
"source"
"infotext"
@@ -440,6 +485,6 @@ headerToTOC = #(define-music-function (parser location header label) (ly:book? s
; we use a delayed stencil to have all the page references available
(ly:make-stencil
`(delay-stencil-evaluation
,(delay (let* ((table (ly:output-def-lookup layout 'label-page-table)))
,(delay (let* ((table (ly:output-def-lookup layout 'label-page-string-table)))
(generate-toc-csv (if (list? table) table '()))
empty-stencil)))))
+65
View File
@@ -0,0 +1,65 @@
#(define (transliterate-de str)
"Gesamte Transliteration: entfernt Diakritika, ersetzt Sonderzeichen, ergibt ASCII-String."
(define (remove-diacritics s)
(string-join
(map (lambda (ch)
(let ((code (char->integer ch)))
;; Unicode-Bereich 0300036F = Combining Diacritical Marks
(if (and (>= code #x0300) (<= code #x036F))
""
(string ch))))
(string->list (string-normalize-nfkd s)))
""))
(define transliteration-table
'(
;; Deutsche Umlaute & ß
("ä" . "ae") ("ö" . "oe") ("ü" . "ue")
("Ä" . "Ae") ("Ö" . "Oe") ("Ü" . "Ue")
("ß" . "ss")
;; Balkan & mitteleuropäische Sonderzeichen
("Č" . "C") ("č" . "c")
("Š" . "S") ("š" . "s")
("Ž" . "Z") ("ž" . "z")
("Đ" . "D") ("đ" . "d")
("Ł" . "L") ("ł" . "l")
("Ø" . "O") ("ø" . "o")
("Æ" . "Ae") ("æ" . "ae")
("Œ" . "Oe") ("œ" . "oe")
;; Kyrillische Buchstaben mit lateinischen Pendants
("А" . "A") ("а" . "a")
("Б" . "B") ("б" . "b")
("В" . "V") ("в" . "v")
("Г" . "G") ("г" . "g")
("Д" . "D") ("д" . "d")
("Е" . "E") ("е" . "e")
("З" . "Z") ("з" . "z")
("И" . "I") ("и" . "i")
("К" . "K") ("к" . "k")
("Л" . "L") ("л" . "l")
("М" . "M") ("м" . "m")
("Н" . "N") ("н" . "n")
("О" . "O") ("о" . "o")
("П" . "P") ("п" . "p")
("Р" . "R") ("р" . "r")
("С" . "S") ("с" . "s")
("Т" . "T") ("т" . "t")
("У" . "U") ("у" . "u")
("Ф" . "F") ("ф" . "f")
("Х" . "Kh") ("х" . "kh")
("Ц" . "Ts") ("ц" . "ts")
("Ч" . "Ch") ("ч" . "ch")
("Ш" . "Sh") ("ш" . "sh")
("Щ" . "Sch") ("щ" . "sch")
("Я" . "Ja") ("я" . "ja")
("Ю" . "Ju") ("ю" . "ju")
("Й" . "J") ("й" . "j")
))
(remove-diacritics
(fold (lambda (pair acc)
(ly:string-substitute (car pair) (cdr pair) acc))
str transliteration-table)))
+1
View File
@@ -1,3 +1,4 @@
\include "../private_includes/book/book_include.ily"
\include "../private_includes/book/transliteration.ily"
\include "../private_includes/book/toc_include.ily"
\include "../private_includes/book/appendix.ily"
+81 -16
View File
@@ -1,8 +1,10 @@
% set the speed of the midi music
#(define midiQuarterNoteSpeed (if (defined? 'midiQuarterNoteSpeed) midiQuarterNoteSpeed 90))
MUSIC = { \transposable #TRANSPOSITION \MUSIC }
LAYOUT = \layout {
\LAYOUT
#(customized-layout LAYOUT)
}
verselayout = \layout {
\LAYOUT
\context {
@@ -11,18 +13,12 @@ verselayout = \layout {
}
}
LAYOUT = \layout {
\LAYOUT
#(let
((custom-size (ly:output-def-lookup LAYOUT 'size #f)))
(if custom-size (layout-set-staff-size custom-size)))
}
TEXT = \markuplist {
\override #`(transposition . ,TRANSPOSITION)
\override #`(verselayout . ,verselayout)
\override #`(verse-chords . ,#{ \chords { \verseChords } #})
\override #`(verse-reference-voice . ,#{ \global \firstVoice #})
\handle-tag-groups \recordedTagGroups
\TEXT
}
@@ -34,6 +30,7 @@ TEXT = \markuplist {
\override #`(verselayout . ,verselayout)
\override #`(verse-chords . ,#{ \chords { \verseChords } #})
\override #`(verse-reference-voice . ,#{ \global \firstVoice #})
\handle-tag-groups \recordedTagGroups
#text
}
#})
@@ -44,6 +41,20 @@ TEXT = \markuplist {
TEXT_PAGES
(list TEXT))))
%% Add invisible ChordPro write trigger to last TEXT_PAGES markuplist
#(when (and (defined? 'chordpro-export-enabled) chordpro-export-enabled (pair? TEXT_PAGES))
(let* ((last-index (- (length TEXT_PAGES) 1))
(last-page (list-ref TEXT_PAGES last-index))
(modified-last-page #{
\markuplist {
#last-page
\chordpro-delayed-write
}
#}))
(set! TEXT_PAGES
(append (list-head TEXT_PAGES last-index)
(list modified-last-page)))))
#(define (add-text-pages text-pages)
(if (pair? text-pages)
(begin
@@ -60,21 +71,75 @@ TEXT = \markuplist {
(if header (set! $defaultheader header))
(if paper (set! $defaultpaper paper))
)
;; Meta-Daten des Liedes als YAML neben die Ausgabedatei schreiben
;; (<ly:parser-output-name>.yml, landet wie das PDF relativ zum
;; Arbeitsverzeichnis). Die Textseiten-Strophen kommen aus dem
;; Render-Collector (chordpro.ily), deshalb wird erst bei der Ausgabe
;; geschrieben: unsichtbarer delayed Stencil an der letzten Textseite.
;; Ohne (nicht-leere) Textseiten gibt es keine Collector-Daten und die
;; yml wird sofort geschrieben.
(when (and (defined? 'yaml-export-enabled) yaml-export-enabled)
;; Collector-Daten auch ohne ChordPro-Flag unter dem Ausgabenamen sammeln
(set! chordpro-default-song-key (ly:parser-output-name))
(if (any pair? TEXT_PAGES)
(let* ((last-index (- (length TEXT_PAGES) 1))
(last-page (list-ref TEXT_PAGES last-index))
(trigger-stencil (yaml-delayed-song-write
(ly:parser-output-name) HEADER MUSIC))
(modified-last-page #{
\markuplist {
#last-page
\stencil #trigger-stencil
}
#}))
(set! TEXT_PAGES
(append (list-head TEXT_PAGES last-index)
(list modified-last-page))))
(scm->yml-file
(string-append (ly:parser-output-name) ".yml")
(extract-song-data HEADER MUSIC #f (ly:parser-output-name)))))
;; ChordPro export (Einzellied): Song-Key ist der Ausgabename (die .cho
;; landet wie das PDF relativ zum Arbeitsverzeichnis); Titel/Autoren
;; kommen aus HEADER in den per-Song-Store.
(when (and (defined? 'chordpro-export-enabled) chordpro-export-enabled)
(set! chordpro-default-song-key (ly:parser-output-name))
(chordpro-register-song-metadata chordpro-default-song-key HEADER))
;; ChordPro_music_collector (Strophen unter den Noten, siehe chordpro.ily)
;; braucht den aktiven Song-Key VOR der Interpretation des Hauptscores.
;; \consists nur bei chordpro-include-music-lyrics -- sonst keine Kosten
;; (Grob-Acknowledger fuer JEDEN Akkord/jede Silbe/Strophenmarker des
;; Hauptscores) fuer Lieder/Buecher, die das Feature nicht nutzen. Bewusst
;; NICHT zusaetzlich an chordpro-export-enabled gekoppelt: die Flag ist
;; eigenstaendig gedacht (steuert nur, ob \chordpro-delayed-write bzw.
;; \write-book-chordpro am Ende tatsaechlich etwas schreiben -- ob dafuer
;; Daten gesammelt wurden, ist eine davon unabhaengige Entscheidung).
(set! chordpro-active-song-key (ly:parser-output-name))
(if (and (defined? 'chordpro-include-music-lyrics) chordpro-include-music-lyrics)
(add-score #{
\score {
\MUSIC
\layout {
\LAYOUT
\context {
\Score
\consists \ChordPro_music_collector
}
}
}#})
(add-score #{
\score {
\MUSIC
\layout { \LAYOUT }
}#})
}#}))
(add-text-pages TEXT_PAGES)
(add-score #{
\score {
\unfoldRepeats { \MUSIC \INLINESCOREMUSIC }
\midi {
\context {
\Score
% Tempo des midi files
tempoWholesPerMinute = #(/ midiQuarterNoteSpeed 4)
}
\context {
\Staff
\remove "Staff_performer"