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
This commit is contained in:
+492
-214
@@ -5,6 +5,199 @@
|
||||
#(use-modules (ice-9 format))
|
||||
#(use-modules (srfi srfi-1))
|
||||
|
||||
%% Per-Song-Store
|
||||
%% ==============
|
||||
%% Der gesamte Sammel-Zustand liegt in einer Struktur pro Lied, gekeyt ueber
|
||||
%% den Liednamen (Buch: songfilename aus \setsongfilename bzw. includeSong;
|
||||
%% Einzellied: ly:parser-output-name). So koennen mehrere Lieder in einem
|
||||
%% Durchlauf (Liederbuch) getrennt gesammelt und geschrieben werden.
|
||||
%%
|
||||
%% Bewusst KEIN srfi-9-Record: Im Buch wird diese Datei pro Lied erneut
|
||||
%% evaluiert (jedes Lied inkludiert base_config -> all.ily), und srfi-9-
|
||||
%% Typen sind generativ -- alte Instanzen wuerden mit neu definierten
|
||||
%% Accessoren kollidieren. Hashtabelle + Prozedur-Accessoren sind gegen
|
||||
%% Re-Evaluation robust; der Store selbst ueberlebt sie per defined?-Idiom.
|
||||
|
||||
#(define chordpro-song-store
|
||||
(if (defined? 'chordpro-song-store) chordpro-song-store (make-hash-table)))
|
||||
|
||||
#(define (chordpro-make-song key)
|
||||
(let ((song (make-hash-table)))
|
||||
(hashq-set! song 'key key)
|
||||
;; Alle Sammel-Listen werden in umgekehrter Reihenfolge aufgebaut (cons)
|
||||
(hashq-set! song 'syllables '())
|
||||
(hashq-set! song 'chords '())
|
||||
(hashq-set! song 'breaks '())
|
||||
(hashq-set! song 'stanza-numbers '())
|
||||
;; Format: (moment verse-idx text direction) fuer Inline-Marker wie repStart/repStop
|
||||
(hashq-set! song 'inline-texts '())
|
||||
;; Zaehlt mit jedem \chordlyrics-Score des Lieds hoch
|
||||
(hashq-set! song 'verse-index 0)
|
||||
;; Dokumentreihenfolge pro Vers: (idx seq pos sub). Die Interpretation
|
||||
;; der Textseiten laeuft in umgekehrter Dokumentreihenfolge, bei
|
||||
;; mehrspaltigen \group-verses zusaetzlich verschraenkt -- seq/pos
|
||||
;; stellen die Quellreihenfolge wieder her (siehe
|
||||
;; chordpro-song-ordered-verse-indices), sub ordnet Sub-Verse aus
|
||||
;; Marker-Splits hinter ihren Elternvers.
|
||||
(hashq-set! song 'verse-order '())
|
||||
;; Akkorde, die {define:}-Direktiven brauchen
|
||||
(hashq-set! song 'minor-chords '())
|
||||
(hashq-set! song 'b-chords '())
|
||||
(hashq-set! song 'h-chords '())
|
||||
(hashq-set! song 'accidental-chords '())
|
||||
(hashq-set! song 'title #f)
|
||||
(hashq-set! song 'authors #f)
|
||||
;; Weitere ChordPro-Metadaten-Direktiven, aus dem Header/der Musik des
|
||||
;; Liedes befuellt (siehe chordpro-register-song-metadata) -- alle #f,
|
||||
;; wenn nicht ermittelbar, dann wird die jeweilige Direktive weggelassen.
|
||||
(hashq-set! song 'copyright #f)
|
||||
(hashq-set! song 'year #f)
|
||||
(hashq-set! song 'key-signature #f)
|
||||
(hashq-set! song 'time #f)
|
||||
(hashq-set! song 'tempo #f)
|
||||
;; Schreibschutz: jedes Lied wird nur einmal geschrieben
|
||||
(hashq-set! song 'written #f)
|
||||
song))
|
||||
|
||||
#(define (chordpro-song-key s) (hashq-ref s 'key))
|
||||
#(define (chordpro-song-syllables s) (hashq-ref s 'syllables))
|
||||
#(define (set-chordpro-song-syllables! s v) (hashq-set! s 'syllables v))
|
||||
#(define (chordpro-song-chords s) (hashq-ref s 'chords))
|
||||
#(define (set-chordpro-song-chords! s v) (hashq-set! s 'chords v))
|
||||
#(define (chordpro-song-breaks s) (hashq-ref s 'breaks))
|
||||
#(define (set-chordpro-song-breaks! s v) (hashq-set! s 'breaks v))
|
||||
#(define (chordpro-song-stanza-numbers s) (hashq-ref s 'stanza-numbers))
|
||||
#(define (set-chordpro-song-stanza-numbers! s v) (hashq-set! s 'stanza-numbers v))
|
||||
#(define (chordpro-song-inline-texts s) (hashq-ref s 'inline-texts))
|
||||
#(define (set-chordpro-song-inline-texts! s v) (hashq-set! s 'inline-texts v))
|
||||
#(define (chordpro-song-verse-index s) (hashq-ref s 'verse-index))
|
||||
#(define (set-chordpro-song-verse-index! s v) (hashq-set! s 'verse-index v))
|
||||
#(define (chordpro-song-verse-order s) (hashq-ref s 'verse-order))
|
||||
#(define (set-chordpro-song-verse-order! s v) (hashq-set! s 'verse-order v))
|
||||
#(define (chordpro-song-minor-chords s) (hashq-ref s 'minor-chords))
|
||||
#(define (set-chordpro-song-minor-chords! s v) (hashq-set! s 'minor-chords v))
|
||||
#(define (chordpro-song-b-chords s) (hashq-ref s 'b-chords))
|
||||
#(define (set-chordpro-song-b-chords! s v) (hashq-set! s 'b-chords v))
|
||||
#(define (chordpro-song-h-chords s) (hashq-ref s 'h-chords))
|
||||
#(define (set-chordpro-song-h-chords! s v) (hashq-set! s 'h-chords v))
|
||||
#(define (chordpro-song-accidental-chords s) (hashq-ref s 'accidental-chords))
|
||||
#(define (set-chordpro-song-accidental-chords! s v) (hashq-set! s 'accidental-chords v))
|
||||
#(define (chordpro-song-title s) (hashq-ref s 'title))
|
||||
#(define (set-chordpro-song-title! s v) (hashq-set! s 'title v))
|
||||
#(define (chordpro-song-authors s) (hashq-ref s 'authors))
|
||||
#(define (set-chordpro-song-authors! s v) (hashq-set! s 'authors v))
|
||||
#(define (chordpro-song-copyright s) (hashq-ref s 'copyright))
|
||||
#(define (set-chordpro-song-copyright! s v) (hashq-set! s 'copyright v))
|
||||
#(define (chordpro-song-year s) (hashq-ref s 'year))
|
||||
#(define (set-chordpro-song-year! s v) (hashq-set! s 'year v))
|
||||
#(define (chordpro-song-key-signature s) (hashq-ref s 'key-signature))
|
||||
#(define (set-chordpro-song-key-signature! s v) (hashq-set! s 'key-signature v))
|
||||
#(define (chordpro-song-time s) (hashq-ref s 'time))
|
||||
#(define (set-chordpro-song-time! s v) (hashq-set! s 'time v))
|
||||
#(define (chordpro-song-tempo s) (hashq-ref s 'tempo))
|
||||
#(define (set-chordpro-song-tempo! s v) (hashq-set! s 'tempo v))
|
||||
#(define (chordpro-song-written s) (hashq-ref s 'written))
|
||||
#(define (set-chordpro-song-written! s v) (hashq-set! s 'written v))
|
||||
|
||||
#(define (chordpro-get-song key)
|
||||
"Store-Eintrag zu key holen, bei Bedarf frisch anlegen."
|
||||
(or (hash-ref chordpro-song-store key)
|
||||
(let ((song (chordpro-make-song key)))
|
||||
(hash-set! chordpro-song-store key song)
|
||||
song)))
|
||||
|
||||
%% Einzellied-Fallback fuer den Song-Key; layout_bottom setzt ihn auf
|
||||
%% (ly:parser-output-name). Im Buch kommt der Key stattdessen aus den
|
||||
%% Markup-Props (\setsongfilename), weil der geklonte Parser nur den
|
||||
%% Buchnamen kennt.
|
||||
#(define chordpro-default-song-key #f)
|
||||
|
||||
#(define (chordpro-resolve-song-key props)
|
||||
"Song-Key aus Markup-Props (Buch) oder dem Einzellied-Fallback."
|
||||
(let ((from-props (and props (chain-assoc-get 'songfilename props #f))))
|
||||
(cond
|
||||
((and (string? from-props) (not (string-null? from-props))) from-props)
|
||||
(chordpro-default-song-key)
|
||||
(else "output"))))
|
||||
|
||||
%% Lied, in dessen Store der Collector-Engraver gerade schreibt. Wird von
|
||||
%% \chordlyrics vor der (synchronen) Score-Interpretation gesetzt.
|
||||
#(define chordpro-active-song-key #f)
|
||||
|
||||
%% Props des laufenden \chordlyrics-Aufrufs -- der Collector loest damit
|
||||
%% Markup-Silben (\updown u.ae.) auf, deren \tag-Sichtbarkeit an
|
||||
%% tags-to-keep/tags-to-remove aus umgebenden \keep-with-tag haengt.
|
||||
#(define chordpro-active-props #f)
|
||||
|
||||
%% Dokumentreihenfolge: \group-verses nimmt sich pro Aufruf eine
|
||||
%% Sequenznummer und stempelt seine Verse mit (seq . pos) in die Props;
|
||||
%% \chordlyrics ausserhalb einer Gruppe nimmt sich selbst eine Nummer.
|
||||
%% Da die Interpretation in umgekehrter Dokumentreihenfolge laeuft,
|
||||
%% ergibt seq absteigend + pos aufsteigend die Quellreihenfolge.
|
||||
#(define chordpro-doc-seq-counter
|
||||
(if (defined? 'chordpro-doc-seq-counter) chordpro-doc-seq-counter 0))
|
||||
#(define chordpro-active-doc-order #f)
|
||||
|
||||
%% Verse-Indizes eines Lieds in Dokumentreihenfolge (fuer Writer/Export).
|
||||
#(define (chordpro-song-ordered-verse-indices song)
|
||||
(let* ((order-alist (chordpro-song-verse-order song))
|
||||
(order-of (lambda (idx) (or (assv idx order-alist) (list idx 0 0 0)))))
|
||||
(sort (iota (chordpro-song-verse-index song))
|
||||
(lambda (a b)
|
||||
(let ((oa (order-of a)) (ob (order-of b)))
|
||||
;; seq absteigend, pos aufsteigend, sub aufsteigend,
|
||||
;; Fallback: idx absteigend
|
||||
(cond
|
||||
((not (= (cadr oa) (cadr ob))) (> (cadr oa) (cadr ob)))
|
||||
((not (= (caddr oa) (caddr ob))) (< (caddr oa) (caddr ob)))
|
||||
((not (= (cadddr oa) (cadddr ob))) (< (cadddr oa) (cadddr ob)))
|
||||
(else (> a b))))))))
|
||||
|
||||
%% Titel/Autoren aus basicSongInfo in den Store uebernehmen. Einzellied:
|
||||
%% layout_bottom nach dem Parsen; Buch: includeSong direkt nach dem Parsen
|
||||
%% des Lieds (basicSongInfo haelt dann genau dieses Lied).
|
||||
%% Musikalische Metadaten (Takt/Tonart/Tempo) kommen aus music->data-alist
|
||||
%% (data_extractor.ily) -- dieselbe Extraktion wie fuer den YAML-Export,
|
||||
%% nur zusaetzlich fuer ChordPro in dessen Notation umgewandelt.
|
||||
#(define (chordpro-register-song-metadata key)
|
||||
(when (defined? 'basicSongInfo)
|
||||
(let* ((header-alist (ly:module->alist basicSongInfo))
|
||||
(title (assoc-ref header-alist 'title))
|
||||
(authors (assoc-ref header-alist 'authors))
|
||||
(copyright (assoc-ref header-alist 'copyright))
|
||||
(year (or (assoc-ref header-alist 'year_text)
|
||||
(assoc-ref header-alist 'year_melody)))
|
||||
(music-alist (and (defined? 'MUSIC) (ly:music? MUSIC) (music->data-alist MUSIC)))
|
||||
(song (chordpro-get-song key)))
|
||||
(when title
|
||||
(set-chordpro-song-title! song
|
||||
(cond ((markup? title) (markup->string title))
|
||||
((string? title) title)
|
||||
(else #f))))
|
||||
(when authors
|
||||
(set-chordpro-song-authors! song authors))
|
||||
(when (and (string? copyright) (not (string-null? copyright)))
|
||||
(set-chordpro-song-copyright! song copyright))
|
||||
(when (and (string? year) (not (string-null? year)))
|
||||
(set-chordpro-song-year! song year))
|
||||
(when music-alist
|
||||
(let ((key-str (assoc-ref music-alist 'key))
|
||||
(time-str (assoc-ref music-alist 'time))
|
||||
(tempo-str (assoc-ref music-alist 'tempo)))
|
||||
(when key-str
|
||||
(set-chordpro-song-key-signature! song (lilypond-key->chordpro-key key-str)))
|
||||
(when time-str
|
||||
(set-chordpro-song-time! song time-str))
|
||||
(when tempo-str
|
||||
(let ((bpm (chordpro-tempo-number tempo-str)))
|
||||
(when bpm (set-chordpro-song-tempo! song bpm)))))))))
|
||||
|
||||
%% Configuration (set by layout_bottom.ily or song file)
|
||||
%% Aktivierbar wie yaml-export-enabled durch Definieren der Variable vor
|
||||
%% dem Laden der Includes.
|
||||
#(define chordpro-export-enabled
|
||||
(if (defined? 'chordpro-export-enabled) chordpro-export-enabled #f))
|
||||
|
||||
%% Helper function to convert German minor chord notation to standard major form
|
||||
%% Used for generating {define:} directives
|
||||
#(define (german-minor-to-major-form chord-str)
|
||||
@@ -96,8 +289,8 @@
|
||||
(else chord-str)))))
|
||||
|
||||
%% Helper function to track minor/B/H/accidental chords and return original
|
||||
#(define (track-and-return-chord chord-str)
|
||||
"Track chords that need {define:} directives and return original."
|
||||
#(define (track-and-return-chord song chord-str)
|
||||
"Track chords that need {define:} directives (per song) and return original."
|
||||
(if (or (not (string? chord-str)) (string-null? chord-str))
|
||||
chord-str
|
||||
(let* ((first-char (string-ref chord-str 0))
|
||||
@@ -106,33 +299,26 @@
|
||||
""))
|
||||
(has-is (string-prefix? "is" rest-str))
|
||||
(has-es (string-prefix? "es" rest-str))
|
||||
(has-accidental (or has-is has-es)))
|
||||
(has-accidental (or has-is has-es))
|
||||
(add-unique! (lambda (getter setter!)
|
||||
(unless (member chord-str (getter song))
|
||||
(setter! song (cons chord-str (getter song)))))))
|
||||
(cond
|
||||
;; H/h chords (German H = English B)
|
||||
((char=? first-char #\H)
|
||||
(unless (member chord-str chordpro-h-chords-used)
|
||||
(set! chordpro-h-chords-used (cons chord-str chordpro-h-chords-used))))
|
||||
((char=? first-char #\h)
|
||||
(unless (member chord-str chordpro-h-chords-used)
|
||||
(set! chordpro-h-chords-used (cons chord-str chordpro-h-chords-used))))
|
||||
((or (char=? first-char #\H) (char=? first-char #\h))
|
||||
(add-unique! chordpro-song-h-chords set-chordpro-song-h-chords!))
|
||||
|
||||
;; B/b chords (German B = English Bb)
|
||||
((char=? first-char #\B)
|
||||
(unless (member chord-str chordpro-b-chords-used)
|
||||
(set! chordpro-b-chords-used (cons chord-str chordpro-b-chords-used))))
|
||||
((char=? first-char #\b)
|
||||
(unless (member chord-str chordpro-b-chords-used)
|
||||
(set! chordpro-b-chords-used (cons chord-str chordpro-b-chords-used))))
|
||||
((or (char=? first-char #\B) (char=? first-char #\b))
|
||||
(add-unique! chordpro-song-b-chords set-chordpro-song-b-chords!))
|
||||
|
||||
;; Chords with accidentals (is/es)
|
||||
(has-accidental
|
||||
(unless (member chord-str chordpro-accidental-chords-used)
|
||||
(set! chordpro-accidental-chords-used (cons chord-str chordpro-accidental-chords-used))))
|
||||
(add-unique! chordpro-song-accidental-chords set-chordpro-song-accidental-chords!))
|
||||
|
||||
;; Other lowercase chords (minor without B/H/accidentals)
|
||||
((char-lower-case? first-char)
|
||||
(unless (member chord-str chordpro-minor-chords-used)
|
||||
(set! chordpro-minor-chords-used (cons chord-str chordpro-minor-chords-used)))))
|
||||
(add-unique! chordpro-song-minor-chords set-chordpro-song-minor-chords!)))
|
||||
|
||||
;; Always return original
|
||||
chord-str)))
|
||||
@@ -197,35 +383,6 @@
|
||||
;; Single chord: return as is
|
||||
clean-str)))
|
||||
|
||||
%% Global state - shared across all engraver instances!
|
||||
#(define chordpro-syllables-collected '())
|
||||
#(define chordpro-chords-collected '())
|
||||
#(define chordpro-breaks-collected '())
|
||||
#(define chordpro-stanza-numbers '())
|
||||
%% Format: (moment verse-idx text direction) for inline text markers like repStart/repStop
|
||||
#(define chordpro-inline-texts-collected '())
|
||||
%%Track break moments to detect verse changes
|
||||
#(define chordpro-seen-break-moments '())
|
||||
%% Increments with each verse (each chordlyrics call)
|
||||
#(define chordpro-current-verse-index 0)
|
||||
%% Collected minor chords (lowercase German chords) for {define:} directives
|
||||
#(define chordpro-minor-chords-used '())
|
||||
%% Collected B chords (German B = English Bb) for {define:} directives
|
||||
#(define chordpro-b-chords-used '())
|
||||
%% Collected H chords (German H = English B) for {define:} directives
|
||||
#(define chordpro-h-chords-used '())
|
||||
%% Collected chords with accidentals (is/es) for {define:} directives
|
||||
#(define chordpro-accidental-chords-used '())
|
||||
|
||||
%% Configuration and metadata (set by layout_bottom.ily or song file)
|
||||
#(define chordpro-export-enabled #f)
|
||||
#(define chordpro-current-filename "output")
|
||||
#(define chordpro-header-title "Untitled")
|
||||
#(define chordpro-header-authors #f)
|
||||
|
||||
%% Write flag (ensures we only write once, not for each finalize call)
|
||||
#(define chordpro-file-written #f)
|
||||
|
||||
%% Helper function to format stanza label for ChordPro
|
||||
#(define (format-chordpro-stanza-label stanza-type stanza-numbers)
|
||||
"Generate ChordPro label based on stanza type and optional numbers"
|
||||
@@ -247,55 +404,68 @@
|
||||
%% Single engraver in Score context
|
||||
#(define ChordPro_score_collector
|
||||
(lambda (context)
|
||||
(let ((pending-syllable #f)
|
||||
(this-verse-index #f))
|
||||
(let ((song #f)
|
||||
(pending-syllable #f)
|
||||
(this-verse-index #f)
|
||||
(doc-seq 0)
|
||||
(doc-pos 0)
|
||||
(sub-counter 0))
|
||||
;; pending-syllable: (moment text verse-idx)
|
||||
(define (save-pending! has-hyphen)
|
||||
(when pending-syllable
|
||||
(set-chordpro-song-syllables! song
|
||||
(cons (list (car pending-syllable) (cadr pending-syllable) has-hyphen (caddr pending-syllable))
|
||||
(chordpro-song-syllables song)))
|
||||
(set! pending-syllable #f)))
|
||||
(define (record-verse-order! idx)
|
||||
(set-chordpro-song-verse-order! song
|
||||
(cons (list idx doc-seq doc-pos sub-counter)
|
||||
(chordpro-song-verse-order song))))
|
||||
(make-engraver
|
||||
;; Initialize - called when engraver is created (once per \chordlyrics)
|
||||
((initialize engraver)
|
||||
;; Each \chordlyrics call creates a new Score, so this marks a new verse
|
||||
(when (> chordpro-current-verse-index 0)
|
||||
;; Reset break tracking for new verse
|
||||
(set! chordpro-seen-break-moments '()))
|
||||
;; Store the verse index for this instance
|
||||
(set! this-verse-index chordpro-current-verse-index)
|
||||
;; Increment global index for next verse
|
||||
(set! chordpro-current-verse-index (+ chordpro-current-verse-index 1)))
|
||||
;; \chordlyrics hat den aktiven Song-Key vor der Interpretation gesetzt;
|
||||
;; jeder \chordlyrics-Score ist ein Vers dieses Lieds.
|
||||
(set! song (chordpro-get-song (or chordpro-active-song-key "output")))
|
||||
(set! this-verse-index (chordpro-song-verse-index song))
|
||||
(set-chordpro-song-verse-index! song (+ this-verse-index 1))
|
||||
(let ((order (or chordpro-active-doc-order (cons 0 0))))
|
||||
(set! doc-seq (car order))
|
||||
(set! doc-pos (cdr order)))
|
||||
(record-verse-order! this-verse-index))
|
||||
|
||||
;; Event listeners
|
||||
(listeners
|
||||
;; LyricEvent from Lyrics context
|
||||
((lyric-event engraver event)
|
||||
(let ((text (ly:event-property event 'text))
|
||||
(moment (ly:context-current-moment context)))
|
||||
(let* ((raw-text (ly:event-property event 'text))
|
||||
;; Markup-Silben (z.B. \updown) mit den Props des
|
||||
;; chordlyrics-Aufrufs zu Strings aufloesen -- erst dabei
|
||||
;; entscheidet sich die \tag-Sichtbarkeit.
|
||||
(text (cond
|
||||
((string? raw-text) raw-text)
|
||||
((markup? raw-text)
|
||||
(markup->string raw-text
|
||||
#:props (or chordpro-active-props '())))
|
||||
(else #f)))
|
||||
(moment (ly:context-current-moment context)))
|
||||
(when (string? text)
|
||||
;; On first lyric event for this engraver instance, capture the verse index
|
||||
(when (not this-verse-index)
|
||||
(set! this-verse-index chordpro-current-verse-index)
|
||||
(set! chordpro-current-verse-index (+ chordpro-current-verse-index 1)))
|
||||
|
||||
;; Save previous syllable if any (it had no hyphen)
|
||||
(when pending-syllable
|
||||
(set! chordpro-syllables-collected
|
||||
(cons (list (car pending-syllable) (cadr pending-syllable) #f (caddr pending-syllable))
|
||||
chordpro-syllables-collected)))
|
||||
(save-pending! #f)
|
||||
;; Store new syllable as pending (with THIS instance's verse index)
|
||||
(set! pending-syllable (list moment text this-verse-index)))))
|
||||
|
||||
;; HyphenEvent from Lyrics context
|
||||
((hyphen-event engraver event)
|
||||
(when pending-syllable
|
||||
(set! chordpro-syllables-collected
|
||||
(cons (list (car pending-syllable) (cadr pending-syllable) #t (caddr pending-syllable))
|
||||
chordpro-syllables-collected))
|
||||
(set! pending-syllable #f)))
|
||||
(save-pending! #t))
|
||||
|
||||
;; BreakEvent - store break moment
|
||||
((break-event engraver event)
|
||||
(let ((moment (ly:context-current-moment context)))
|
||||
|
||||
;; Store break with this instance's verse index
|
||||
(set! chordpro-breaks-collected
|
||||
(cons (cons moment this-verse-index) chordpro-breaks-collected)))))
|
||||
(set-chordpro-song-breaks! song
|
||||
(cons (cons moment this-verse-index) (chordpro-song-breaks song))))))
|
||||
|
||||
;; Acknowledge grobs from child contexts
|
||||
(acknowledgers
|
||||
@@ -304,7 +474,10 @@
|
||||
(let* ((details (ly:grob-property grob 'details '()))
|
||||
(custom-realstanza (ly:assoc-get 'custom-realstanza details #f))
|
||||
(stanza-type (ly:assoc-get 'custom-stanza-type details 'verse))
|
||||
(stanza-numbers (ly:assoc-get 'custom-stanza-numbers details '()))
|
||||
;; \override-stanza ueberschreibt die GEDRUCKTEN Nummern
|
||||
;; (z.B. "Ref. 4:" statt "Ref. 1, 4:") -- die zaehlen.
|
||||
(stanza-numbers (or (ly:assoc-get 'custom-stanzanumber-override details #f)
|
||||
(ly:assoc-get 'custom-stanza-numbers details '())))
|
||||
;; Check for custom inline text (e.g., repStart/repStop with direction)
|
||||
(custom-inline-text (ly:assoc-get 'custom-inline-text details #f))
|
||||
(custom-inline-direction (ly:assoc-get 'custom-inline-direction details CENTER))
|
||||
@@ -327,53 +500,115 @@
|
||||
(if stanza-markup
|
||||
(format #f "~a" stanza-markup)
|
||||
#f))))
|
||||
(existing (assoc this-verse-index chordpro-stanza-numbers))
|
||||
(existing (assoc this-verse-index (chordpro-song-stanza-numbers song)))
|
||||
(moment (ly:context-current-moment (ly:translator-context source-engraver)))
|
||||
(direction (ly:grob-property grob 'direction CENTER)))
|
||||
|
||||
;; If custom-inline-text is set, always collect it as inline text (regardless of realstanza)
|
||||
;; This allows repStart/repStop to be collected even when combined with real stanza numbers
|
||||
(when custom-inline-text
|
||||
(set! chordpro-inline-texts-collected
|
||||
(set-chordpro-song-inline-texts! song
|
||||
(cons (list moment this-verse-index custom-inline-text custom-inline-direction)
|
||||
chordpro-inline-texts-collected)))
|
||||
(chordpro-song-inline-texts song))))
|
||||
|
||||
;; If this is NOT a real stanza marker (custom-realstanza not set or ##f),
|
||||
;; collect it as inline text with direction info
|
||||
(when (and (not custom-realstanza) (not custom-inline-text))
|
||||
(when stanza-text ; Only if there's text to display
|
||||
(set! chordpro-inline-texts-collected
|
||||
(cons (list moment this-verse-index stanza-text direction)
|
||||
chordpro-inline-texts-collected))))
|
||||
(if (and (string? stanza-markup) (not existing))
|
||||
;; Ein direktes \set stanza = "..." (String, kein Markup)
|
||||
;; am Versanfang ist ein echtes Label (z.B. "2.b"), keine
|
||||
;; Dekoration -- als weiches Stanza-Label uebernehmen
|
||||
;; (realstanza #f), nicht in den Verskoerper.
|
||||
(set-chordpro-song-stanza-numbers! song
|
||||
(cons (list this-verse-index stanza-markup 'verse #f)
|
||||
(chordpro-song-stanza-numbers song)))
|
||||
(when stanza-text ; Only if there's text to display
|
||||
(set-chordpro-song-inline-texts! song
|
||||
(cons (list moment this-verse-index stanza-text direction)
|
||||
(chordpro-song-inline-texts song))))))
|
||||
|
||||
;; Store or update stanza info for this verse (only if it's a real stanza marker)
|
||||
;; Entry format: (verse-idx stanza-text stanza-type realstanza-flag)
|
||||
;; Entry format: (verse-idx stanza-text stanza-type realstanza-flag numbers)
|
||||
(when custom-realstanza
|
||||
(if existing
|
||||
;; Update existing entry with type and text if available
|
||||
;; IMPORTANT: Don't overwrite realstanza=##t markers and their text
|
||||
(let* ((existing-text (cadr existing))
|
||||
(existing-type (caddr existing))
|
||||
(existing-realstanza (if (> (length existing) 3) (cadddr existing) #f)))
|
||||
(set! chordpro-stanza-numbers
|
||||
(let* ((existing-text (and existing (cadr existing)))
|
||||
(existing-type (and existing (caddr existing)))
|
||||
(existing-realstanza (and existing (> (length existing) 3) (cadddr existing)))
|
||||
(existing-numbers (or (and existing (> (length existing) 4) (list-ref existing 4)) '()))
|
||||
;; Reprint desselben Markers (Strophennummer am Zeilen-
|
||||
;; umbruch erneut gedruckt, ggf. als Nummern-Obermenge
|
||||
;; wie #(stanza 2 3) nach Strophe 2) setzt den Vers
|
||||
;; fort; vgl. merge-reprinted im data_extractor.
|
||||
(same-marker?
|
||||
(and existing-realstanza
|
||||
(or (and (string? existing-text) (string? stanza-text)
|
||||
(string=? existing-text stanza-text))
|
||||
(and (eq? existing-type stanza-type)
|
||||
(pair? existing-numbers)
|
||||
(every (lambda (n) (member n stanza-numbers))
|
||||
existing-numbers))))))
|
||||
(cond
|
||||
;; Kein Eintrag: neu anlegen
|
||||
((not existing)
|
||||
(set-chordpro-song-stanza-numbers! song
|
||||
(cons (list this-verse-index stanza-text stanza-type custom-realstanza stanza-numbers)
|
||||
(chordpro-song-stanza-numbers song))))
|
||||
;; Weicher Eintrag (\set stanza) oder Reprint: aktualisieren,
|
||||
;; Text echter Marker nicht ueberschreiben
|
||||
((or (not existing-realstanza) same-marker?)
|
||||
(set-chordpro-song-stanza-numbers! song
|
||||
(map (lambda (entry)
|
||||
(if (= (car entry) this-verse-index)
|
||||
(list (car entry)
|
||||
;; If existing entry is a real stanza, don't overwrite text
|
||||
;; Otherwise, use new text if available
|
||||
(if existing-realstanza
|
||||
existing-text
|
||||
(or stanza-text existing-text))
|
||||
;; Update type only if custom-realstanza is set
|
||||
(if custom-realstanza stanza-type existing-type)
|
||||
;; Preserve ##t realstanza flag
|
||||
(or existing-realstanza custom-realstanza))
|
||||
stanza-type
|
||||
#t
|
||||
(if existing-realstanza existing-numbers stanza-numbers))
|
||||
entry))
|
||||
chordpro-stanza-numbers)))
|
||||
;; Create new entry with type, text, and realstanza flag
|
||||
(set! chordpro-stanza-numbers
|
||||
(cons (list this-verse-index stanza-text stanza-type custom-realstanza)
|
||||
chordpro-stanza-numbers)))))) ; closes set!, if, when, stanza-number-interface
|
||||
(chordpro-song-stanza-numbers song))))
|
||||
;; ANDERER echter Marker im selben \chordlyrics (z.B.
|
||||
;; Refrain nach der Strophe): neuen logischen Vers beginnen.
|
||||
;; Alles ab hier (inkl. einer im selben Timestep schon
|
||||
;; angefallenen Silbe) gehoert zum neuen Vers.
|
||||
(else
|
||||
(let ((old-idx this-verse-index)
|
||||
(split-idx (chordpro-song-verse-index song)))
|
||||
(set-chordpro-song-verse-index! song (+ split-idx 1))
|
||||
(set! sub-counter (+ sub-counter 1))
|
||||
(set! this-verse-index split-idx)
|
||||
(record-verse-order! split-idx)
|
||||
;; Die erste Silbe des neuen Abschnitts faellt in
|
||||
;; denselben Timestep wie der Marker und wurde ggf.
|
||||
;; schon gesammelt (pending oder -- bei Trennstrich --
|
||||
;; bereits gespeichert): dem neuen Vers zuschlagen.
|
||||
(when (and pending-syllable
|
||||
(ly:moment=? (car pending-syllable) moment))
|
||||
(set! pending-syllable
|
||||
(list moment (cadr pending-syllable) split-idx)))
|
||||
(let retag ((rest (chordpro-song-syllables song)) (acc '()))
|
||||
(if (and (pair? rest)
|
||||
(= (cadddr (car rest)) old-idx)
|
||||
(ly:moment=? (car (car rest)) moment))
|
||||
(retag (cdr rest)
|
||||
(cons (list (car (car rest)) (cadr (car rest))
|
||||
(caddr (car rest)) split-idx)
|
||||
acc))
|
||||
(set-chordpro-song-syllables! song
|
||||
(append (reverse acc) rest))))
|
||||
(let retag ((rest (chordpro-song-chords song)) (acc '()))
|
||||
(if (and (pair? rest)
|
||||
(= (caddr (car rest)) old-idx)
|
||||
(ly:moment=? (car (car rest)) moment))
|
||||
(retag (cdr rest)
|
||||
(cons (list (car (car rest)) (cadr (car rest))
|
||||
split-idx (cadddr (car rest)))
|
||||
acc))
|
||||
(set-chordpro-song-chords! song
|
||||
(append (reverse acc) rest))))
|
||||
(set-chordpro-song-stanza-numbers! song
|
||||
(cons (list split-idx stanza-text stanza-type custom-realstanza stanza-numbers)
|
||||
(chordpro-song-stanza-numbers song)))))))))) ; closes stanza-number-interface
|
||||
|
||||
;; ChordName grobs from ChordNames context
|
||||
;; Store grob reference - visibility will be recorded by ChordPro_chord_visibility_recorder
|
||||
@@ -387,8 +622,8 @@
|
||||
(chord-name-final
|
||||
(if (and alt-main-name alt-alt-name)
|
||||
;; This is an altChord - use the pre-extracted names
|
||||
(let ((main (track-and-return-chord alt-main-name))
|
||||
(alt (track-and-return-chord alt-alt-name)))
|
||||
(let ((main (track-and-return-chord song alt-main-name))
|
||||
(alt (track-and-return-chord song alt-alt-name)))
|
||||
;; Check if main is empty - if so, only output alt in parens
|
||||
(if (string-null? main)
|
||||
(string-append "[(" alt ")]") ; Only alt chord: [(D7)]
|
||||
@@ -409,11 +644,11 @@
|
||||
(second-part (if (and open-pos close-pos)
|
||||
(substring rest (+ open-pos 1) close-pos)
|
||||
""))
|
||||
(first-converted (track-and-return-chord first-part))
|
||||
(second-converted (track-and-return-chord second-part)))
|
||||
(first-converted (track-and-return-chord song first-part))
|
||||
(second-converted (track-and-return-chord song second-part)))
|
||||
(string-append first-converted "][(" second-converted ")"))
|
||||
;; Single chord: just track and return original
|
||||
(track-and-return-chord chord-names-str))))))
|
||||
(track-and-return-chord song chord-names-str))))))
|
||||
;; Store grob reference along with chord data (unless empty)
|
||||
;; Format: (moment chord-name verse-index grob)
|
||||
;; Filter out completely empty chords, but convert ][(X) to [(X)]
|
||||
@@ -432,8 +667,8 @@
|
||||
;; Normal chord
|
||||
(else chord-name-final))))
|
||||
(when processed-chord-name
|
||||
(set! chordpro-chords-collected
|
||||
(cons (list moment processed-chord-name this-verse-index grob) chordpro-chords-collected))))))
|
||||
(set-chordpro-song-chords! song
|
||||
(cons (list moment processed-chord-name this-verse-index grob) (chordpro-song-chords song)))))))
|
||||
|
||||
;; LyricText grobs to get stanza number
|
||||
((lyric-syllable-interface engraver grob source-engraver)
|
||||
@@ -448,7 +683,7 @@
|
||||
(when stanza-value
|
||||
;; Only update existing stanza entries (don't create new ones)
|
||||
;; New entries should only be created by stanza-number-interface
|
||||
(let* ((existing (assoc this-verse-index chordpro-stanza-numbers))
|
||||
(let* ((existing (assoc this-verse-index (chordpro-song-stanza-numbers song)))
|
||||
(existing-text (if existing (cadr existing) #f))
|
||||
(stanza-text (if (markup? stanza-value)
|
||||
(markup->string stanza-value)
|
||||
@@ -459,7 +694,7 @@
|
||||
(string? stanza-text)
|
||||
(not (string-null? stanza-text)))
|
||||
;; Update existing entry with text (keep type and realstanza flag)
|
||||
(set! chordpro-stanza-numbers
|
||||
(set-chordpro-song-stanza-numbers! song
|
||||
(map (lambda (entry)
|
||||
(if (= (car entry) this-verse-index)
|
||||
(list (car entry)
|
||||
@@ -467,92 +702,70 @@
|
||||
(caddr entry) ; Keep stanza-type
|
||||
(if (> (length entry) 3) (cadddr entry) #f)) ; Keep realstanza-flag if exists
|
||||
entry))
|
||||
chordpro-stanza-numbers))))))) ; schließt lyric-syllable-interface
|
||||
(chordpro-song-stanza-numbers song)))))))) ; schließt lyric-syllable-interface
|
||||
) ; schließt acknowledgers
|
||||
;; End of timestep
|
||||
((stop-translation-timestep engraver)
|
||||
(when pending-syllable
|
||||
(set! chordpro-syllables-collected
|
||||
(cons (list (car pending-syllable) (cadr pending-syllable) #f (caddr pending-syllable))
|
||||
chordpro-syllables-collected))
|
||||
(set! pending-syllable #f)))
|
||||
(save-pending! #f))
|
||||
|
||||
;; Finalize - just write debug, don't filter chords yet
|
||||
;; (Filtering happens in chordpro-write-from-engraver-data where grob properties are final)
|
||||
;; Finalize - just save leftovers, don't filter chords yet
|
||||
;; (Filtering happens in chordpro-write-song where grob properties are final)
|
||||
((finalize engraver)
|
||||
;; Save any remaining pending syllable
|
||||
(when pending-syllable
|
||||
(set! chordpro-syllables-collected
|
||||
(cons (list (car pending-syllable) (cadr pending-syllable) #f (caddr pending-syllable))
|
||||
chordpro-syllables-collected))
|
||||
(set! pending-syllable #f))
|
||||
)))))
|
||||
(save-pending! #f))))))
|
||||
|
||||
%% Helper functions to format and write ChordPro
|
||||
#(define (chordpro-write-from-engraver-data num-verses)
|
||||
"Write ChordPro file from collected engraver data"
|
||||
(let* ((filename (if (defined? 'chordpro-current-filename)
|
||||
chordpro-current-filename
|
||||
"output"))
|
||||
(output-file (string-append filename ".cho"))
|
||||
#(define (chordpro-format-song song)
|
||||
"Format one song's collected engraver data as a ChordPro string."
|
||||
(let* ((num-verses (chordpro-song-verse-index song))
|
||||
;; Reverse all lists (they were collected in reverse order)
|
||||
(syllables (reverse chordpro-syllables-collected))
|
||||
(chords (reverse chordpro-chords-collected))
|
||||
(breaks (reverse chordpro-breaks-collected)))
|
||||
(syllables (reverse (chordpro-song-syllables song)))
|
||||
(chords (reverse (chordpro-song-chords song)))
|
||||
(breaks (reverse (chordpro-song-breaks song)))
|
||||
(stanza-numbers (chordpro-song-stanza-numbers song))
|
||||
(inline-texts (chordpro-song-inline-texts song))
|
||||
(write-defines (lambda (chord-list convert)
|
||||
(for-each
|
||||
(lambda (chord)
|
||||
(display (format #f "{define: ~a copy ~a}\n" chord (convert chord))))
|
||||
(reverse chord-list)))))
|
||||
|
||||
(with-output-to-file output-file
|
||||
(with-output-to-string
|
||||
(lambda ()
|
||||
;; Write metadata
|
||||
(display (format #f "{title: ~a}\n"
|
||||
(if (defined? 'chordpro-header-title)
|
||||
chordpro-header-title
|
||||
"Untitled")))
|
||||
(when (and (defined? 'chordpro-header-authors) chordpro-header-authors)
|
||||
(display (format #f "{artist: ~a}\n"
|
||||
(format-chordpro-authors chordpro-header-authors))))
|
||||
(display (format #f "{title: ~a}\n" (or (chordpro-song-title song) "Untitled")))
|
||||
(when (chordpro-song-authors song)
|
||||
(for-each (lambda (name) (display (format #f "{artist: ~a}\n" name)))
|
||||
(chordpro-all-author-names (chordpro-song-authors song)))
|
||||
(for-each (lambda (name) (display (format #f "{composer: ~a}\n" name)))
|
||||
(chordpro-authors-by-role 'melody (chordpro-song-authors song)))
|
||||
(for-each (lambda (name) (display (format #f "{lyricist: ~a}\n" name)))
|
||||
(chordpro-authors-by-role 'text (chordpro-song-authors song))))
|
||||
(when (chordpro-song-copyright song)
|
||||
(display (format #f "{copyright: ~a}\n" (chordpro-song-copyright song))))
|
||||
(when (chordpro-song-year song)
|
||||
(display (format #f "{year: ~a}\n" (chordpro-song-year song))))
|
||||
(when (chordpro-song-key-signature song)
|
||||
(display (format #f "{key: ~a}\n" (chordpro-song-key-signature song))))
|
||||
(when (chordpro-song-time song)
|
||||
(display (format #f "{time: ~a}\n" (chordpro-song-time song))))
|
||||
(when (chordpro-song-tempo song)
|
||||
(display (format #f "{tempo: ~a}\n" (chordpro-song-tempo song))))
|
||||
|
||||
;; Write {define:} directives for all used minor chords
|
||||
(unless (null? chordpro-minor-chords-used)
|
||||
(newline)
|
||||
(for-each
|
||||
(lambda (minor-chord)
|
||||
;; Convert minor chord to major form for the definition
|
||||
(let ((major-form (german-minor-to-major-form minor-chord)))
|
||||
(display (format #f "{define: ~a copy ~a}\n" minor-chord major-form))))
|
||||
(reverse chordpro-minor-chords-used)))
|
||||
|
||||
;; Write {define:} directives for all used B chords (German B = English Bb)
|
||||
(unless (null? chordpro-b-chords-used)
|
||||
(for-each
|
||||
(lambda (b-chord)
|
||||
;; Convert B/b to Bb/Bbm form for the definition
|
||||
(let ((bb-form (german-b-to-bb-form b-chord)))
|
||||
(display (format #f "{define: ~a copy ~a}\n" b-chord bb-form))))
|
||||
(reverse chordpro-b-chords-used)))
|
||||
|
||||
;; Write {define:} directives for all used H chords (German H = English B)
|
||||
(unless (null? chordpro-h-chords-used)
|
||||
(for-each
|
||||
(lambda (h-chord)
|
||||
;; Convert H/h to B/Bm form for the definition
|
||||
(let ((b-form (german-h-to-b-form h-chord)))
|
||||
(display (format #f "{define: ~a copy ~a}\n" h-chord b-form))))
|
||||
(reverse chordpro-h-chords-used)))
|
||||
|
||||
;; Write {define:} directives for chords with accidentals (is/es -> #/b)
|
||||
(unless (null? chordpro-accidental-chords-used)
|
||||
(for-each
|
||||
(lambda (accidental-chord)
|
||||
;; Convert German accidentals to international format
|
||||
(let ((intl-form (german-accidentals-to-international accidental-chord)))
|
||||
(display (format #f "{define: ~a copy ~a}\n" accidental-chord intl-form))))
|
||||
(reverse chordpro-accidental-chords-used)))
|
||||
;; {define:} directives: minor chords (lowercase German), B/b (German B =
|
||||
;; English Bb), H/h (German H = English B), accidentals (is/es -> #/b)
|
||||
(unless (null? (chordpro-song-minor-chords song))
|
||||
(newline))
|
||||
(write-defines (chordpro-song-minor-chords song) german-minor-to-major-form)
|
||||
(write-defines (chordpro-song-b-chords song) german-b-to-bb-form)
|
||||
(write-defines (chordpro-song-h-chords song) german-h-to-b-form)
|
||||
(write-defines (chordpro-song-accidental-chords song) german-accidentals-to-international)
|
||||
|
||||
(newline)
|
||||
|
||||
;; Write each verse in reverse order (they were collected backwards)
|
||||
(let loop ((verse-idx (- num-verses 1)))
|
||||
(when (>= verse-idx 0)
|
||||
;; Verse in Dokumentreihenfolge schreiben
|
||||
(for-each
|
||||
(lambda (verse-idx)
|
||||
;; Get syllables, chords and breaks for this verse
|
||||
(let* ((verse-syllables (filter (lambda (s) (= (cadddr s) verse-idx)) syllables))
|
||||
(verse-chords-raw (filter (lambda (c)
|
||||
@@ -576,9 +789,9 @@
|
||||
(cons (list (car chord) chord-name (caddr chord)) result)))))))
|
||||
(verse-breaks (filter (lambda (b) (= (cdr b) verse-idx)) breaks))
|
||||
(verse-inline-texts (filter (lambda (r) (= (cadr r) verse-idx))
|
||||
(reverse chordpro-inline-texts-collected)))
|
||||
(reverse inline-texts)))
|
||||
;; Get stanza info (number, type, and realstanza flag) for this verse
|
||||
(stanza-entry (find (lambda (s) (= (car s) verse-idx)) chordpro-stanza-numbers))
|
||||
(stanza-entry (find (lambda (s) (= (car s) verse-idx)) stanza-numbers))
|
||||
(stanza-num (if stanza-entry (cadr stanza-entry) #f))
|
||||
(stanza-type (if stanza-entry (caddr stanza-entry) 'verse))
|
||||
(realstanza (if stanza-entry (cadddr stanza-entry) #f))
|
||||
@@ -607,11 +820,15 @@
|
||||
(when (and stanza-num (not (string-null? stanza-num)))
|
||||
(display (format #f "# ~a\n" stanza-num)))
|
||||
(display (format-verse-as-chordpro verse-syllables verse-chords verse-breaks verse-inline-texts))
|
||||
(display "\n\n"))))
|
||||
(display "\n\n"))))))
|
||||
(chordpro-song-ordered-verse-indices song))))))
|
||||
|
||||
(loop (- verse-idx 1)))))))
|
||||
|
||||
))
|
||||
%% Schreibt ein Lied als eigene <key>.cho-Datei (Einzellied-/Pro-Song-Modus).
|
||||
#(define (chordpro-write-song song)
|
||||
"Write one song's ChordPro file from its collected engraver data"
|
||||
(let ((output-file (string-append (chordpro-song-key song) ".cho")))
|
||||
(with-output-to-file output-file
|
||||
(lambda () (display (chordpro-format-song song))))))
|
||||
|
||||
#(define (format-verse-as-chordpro syllables chords breaks inline-texts)
|
||||
"Format one verse as ChordPro text with line breaks.
|
||||
@@ -963,35 +1180,96 @@
|
||||
"Add two moments"
|
||||
(ly:moment-sub m1 (ly:moment-sub (ly:make-moment 0) m2)))
|
||||
|
||||
#(define (format-chordpro-authors authors)
|
||||
"Format authors list for ChordPro"
|
||||
(cond
|
||||
((list? authors)
|
||||
(string-join
|
||||
(filter (lambda (s) (not (string-null? s)))
|
||||
(map (lambda (author-info)
|
||||
(cond
|
||||
((pair? author-info) (car author-info))
|
||||
((string? author-info) author-info)
|
||||
(else "")))
|
||||
authors))
|
||||
", "))
|
||||
(else "")))
|
||||
%% Lieferbarer Name eines Autors-Keys (z.B. "HansLeip") aus AUTHOR_DATA
|
||||
%% (Feld "name", z.B. "Hans Leip"); ohne Eintrag der rohe Key als Fallback,
|
||||
%% analog zu format-author in footer_with_songinfo.ily -- aber ohne dessen
|
||||
%% Praefixe/Jahresangaben, die fuer ChordPro-Metadaten nicht passen.
|
||||
#(define (chordpro-author-display-name author-id)
|
||||
(let ((data (and (defined? 'AUTHOR_DATA) (assoc-ref AUTHOR_DATA author-id))))
|
||||
(or (and data (assoc-ref data "name")) author-id)))
|
||||
|
||||
%% Formatierte Namen aller Autoren (unabhaengig von ihrer Rolle) als Liste
|
||||
%% -- fuer je eine eigene {artist:}-Zeile. ChordPro will mehrere Werte
|
||||
%% nicht kommagetrennt in einer Direktive, sondern als mehrfache Direktive
|
||||
%% (https://www.chordpro.org/chordpro/directives-artist/: "Multiple
|
||||
%% artists can be specified using multiple directives.").
|
||||
#(define (chordpro-all-author-names authors)
|
||||
(if (list? authors)
|
||||
(filter (lambda (s) (not (string-null? s)))
|
||||
(map (lambda (author-info)
|
||||
(cond
|
||||
((pair? author-info) (chordpro-author-display-name (car author-info)))
|
||||
((string? author-info) (chordpro-author-display-name author-info))
|
||||
(else "")))
|
||||
authors))
|
||||
'()))
|
||||
|
||||
%% Formatierte Namen aller Autoren mit dieser Rolle (z.B. 'melody fuer
|
||||
%% {composer:}, 'text fuer {lyricist:}) als Liste, aus demselben Grund wie
|
||||
%% chordpro-all-author-names eine Liste statt ein komma-getrennter String.
|
||||
%% authors ist die rohe Liste aus dem Header, z.B.
|
||||
%% (("HansLeip" text melody) ("alf" melody)).
|
||||
#(define (chordpro-authors-by-role role authors)
|
||||
(if (list? authors)
|
||||
(filter-map (lambda (author-info)
|
||||
(and (pair? author-info)
|
||||
(member role (cdr author-info))
|
||||
(chordpro-author-display-name (car author-info))))
|
||||
authors)
|
||||
'()))
|
||||
|
||||
%% Wandelt eine LilyPond-Tonart wie "d minor"/"es major" (Ausgabe von
|
||||
%% key-music->string in data_extractor.ily) in ChordPro-Tonart-Notation um
|
||||
%% (z.B. "Dm", "Eb"). Nutzt dieselbe internationale Notenlogik wie die
|
||||
%% {define:}-Direktiven, aber auf die BARE Note angewendet statt auf einen
|
||||
%% vollen Akkordnamen -- Dur/Moll steht hier als eigenes Wort, nicht als
|
||||
%% Gross-/Kleinschreibung wie bei Akkorden aus \chordmode.
|
||||
#(define (lilypond-key->chordpro-key key-string)
|
||||
(let* ((parts (string-split key-string #\space))
|
||||
(note (car parts))
|
||||
(minor? (string=? (cadr parts) "minor"))
|
||||
(first-char (string-ref note 0))
|
||||
(rest (if (> (string-length note) 1) (substring note 1) "")))
|
||||
(string-append
|
||||
(cond
|
||||
((char=? first-char #\h) "B") ; deutsches H = engl. B
|
||||
((and (char=? first-char #\b) (string-null? rest)) "Bb") ; deutsches b = engl. Bb
|
||||
(else (string (char-upcase first-char))))
|
||||
(cond
|
||||
((string=? rest "is") "#") ; fis/cis/... = Kreuz
|
||||
((member note '("es" "as")) "b") ; unregelmaessig: es=Eb, as=Ab
|
||||
((string=? rest "es") "b") ; des/ges/ces = regulaeres -es
|
||||
(else ""))
|
||||
(if minor? "m" ""))))
|
||||
|
||||
%% Extrahiert die BPM-Zahl aus einem tempo-music->string-Ergebnis wie
|
||||
%% "4 = 130" oder "Allegro (4 = 130)"; #f bei reinem Text-Tempo ("Allegro",
|
||||
%% kein Metronomwert) oder wenn tempo-string selbst #f ist. Bei Bereichen
|
||||
%% ("4. = 60-70") wird die erste Zahl genommen.
|
||||
#(define (chordpro-tempo-number tempo-string)
|
||||
(and tempo-string
|
||||
(let ((match (ly:regex-exec (ly:make-regex "=\\s*([0-9]+)") tempo-string)))
|
||||
(and match (ly:regex-match-substring match 1)))))
|
||||
|
||||
%% Define markup command for delayed ChordPro write BEFORE modifying TEXT_PAGES
|
||||
%% Uses delay-stencil-evaluation to write after all engraver data is collected
|
||||
#(define-markup-command (chordpro-delayed-write layout props)
|
||||
()
|
||||
#:category other
|
||||
"Invisible markup that writes ChordPro file during stencil evaluation (after all engraver data is collected)"
|
||||
;; We use a delayed stencil to ensure all engraver data is available
|
||||
(ly:make-stencil
|
||||
`(delay-stencil-evaluation
|
||||
,(delay (begin
|
||||
(when (and (defined? 'chordpro-export-enabled)
|
||||
chordpro-export-enabled
|
||||
(not chordpro-file-written)
|
||||
(> chordpro-current-verse-index 0))
|
||||
(chordpro-write-from-engraver-data chordpro-current-verse-index)
|
||||
(set! chordpro-file-written #t))
|
||||
empty-stencil)))))
|
||||
"Invisible markup that writes one song's ChordPro file during stencil
|
||||
evaluation (after all engraver data is collected). The song is identified at
|
||||
interpretation time: in books via the songfilename markup prop
|
||||
(\\setsongfilename), standalone via the chordpro-default-song-key fallback."
|
||||
(let ((key (chordpro-resolve-song-key props)))
|
||||
(ly:make-stencil
|
||||
`(delay-stencil-evaluation
|
||||
,(delay (begin
|
||||
(when (and (defined? 'chordpro-export-enabled)
|
||||
chordpro-export-enabled)
|
||||
(let ((song (hash-ref chordpro-song-store key)))
|
||||
(when (and song
|
||||
(not (chordpro-song-written song))
|
||||
(> (chordpro-song-verse-index song) 0))
|
||||
(chordpro-write-song song)
|
||||
(set-chordpro-song-written! song #t))))
|
||||
empty-stencil))))))
|
||||
|
||||
Reference in New Issue
Block a user