Le 17/11/2022 à 23:02, Jean Abou Samra a écrit :
Le 17/11/2022 à 23:01, Werner LEMBERG a écrit :But how do you take into account the constraints from lyrics here? That is the whole problem.They should be completely ignored.In that case, it should be completely doable even in Scheme now. (I started writing some code yesterday in response to Abraham that does essentially that, maybe I'll have it finished today).Aah, nice!No so fast, wait until it actually works before calling it nice, otherwise if it doesn't work I'll feel bad :-)
Here you go. This is a vast refinement of the initial \autoMove function that takes all lyric words into account simultaneously, finding an optimal solution that minimizes the distance from each lyric word to its note while respecting the minimal distances between lyric words as configured by LyricSpace.minimum-distance / LyricHyphen.minimum-distance. I used the first algorithm with acceptable complexity that I could think of, so there may be a simpler solution for this. Nevertheless, it runs in linear time, so I'm happy enough. (If it were quadratic or worse, the running time could potentially be problematic if ly:one-line-auto-height breaking is used, in which case there can be lots of lyrics on the same line.) Caveats: - As said in a previous message, this is the other extreme compared to what LilyPond does by default: lyrics are not taken into account *at all* into note spacing. In particular, LilyPond may produce a page breaking configuration in which there are too many notes on the same system to fit the lyrics comfortably. I expect that using \break in those cases will often be enough to get an acceptable result, but I have zero experience with typesetting music with lyrics. (And little actual practical experience with typesetting music at all, to be honest.) - I have barely tested it. Additional featurelets: - You can still set LyricText.self-alignment-X. It defines the position of the LyricText that the algorithm will consider optimal. - You can set LyricText.details.strength to tell the algorithm to place one specific syllable closer to its note at the expense of the others. See the example. Before someone asks: this is not good to integrate into LilyPond as-is, because it breaks assumptions that grob implementors and LilyPond in general make (namely, the assumption that the X-offset of an item is known before line breaking). One would have to either go for a full solution as brainstormed in one of my earlier messages, or at least find a way for an Item to declare that its positioning depends on other things (akin to the current cross-staff property, but preferably less invasive). Hope that helps, Jean
% Copyright (C) 2022, Jean Abou Samra <j...@abou-samra.fr> % Placed under the Creative Commons CC0 1.0 Universal license. \version "2.23.81" #(ly:set-option 'compile-scheme-code) #(use-modules (ice-9 match) (ice-9 hash-table) (oop goops)) %% convenience stuff: #(define-syntax-rule (transform! lval proc) (set! lval (proc lval))) #(define -> (make-procedure-with-setter (lambda (instance . path) (let loop ((instance instance) (path path)) (match path ((slot) (slot-ref instance slot)) ((slot . rest) (loop (slot-ref instance slot) rest))))) (lambda (instance . args) (let loop ((instance instance) (args args)) (match args ((slot new) (slot-set! instance slot new)) ((slot . rest) (loop (slot-ref instance slot) rest))))))) #(define-class <lyric-variable> () (ideal #:init-keyword #:ideal) (extent #:init-keyword #:extent) (strength #:init-keyword #:strength) (tied-to #:init-value #f) (tied-offset #:init-value #f) (final #:init-value #f)) #(define (merged-variable! group var) (let* ((delta (- (interval-end (-> group 'extent)) (interval-start (-> var 'extent)))) (new (make <lyric-variable> #:ideal (/ (+ (* (-> group 'strength) (-> group 'ideal)) (* (-> var 'strength) (- (-> var 'ideal) delta))) (+ (-> group 'strength) (-> var 'strength))) #:extent (cons (interval-start (-> group 'extent)) (+ (interval-end (-> group 'extent)) (interval-length (-> var 'extent)))) #:strength (+ (-> group 'strength) (-> var 'strength))))) (set! (-> group 'tied-to) new) (set! (-> group 'tied-offset) 0) (set! (-> var 'tied-to) new) (set! (-> var 'tied-offset) delta) new)) #(define (propagate! variables) (match variables ((var) variables) ((var group . rest) (let ((have-overlap (<= (+ (-> var 'ideal) (interval-start (-> var 'extent))) (+ (-> group 'ideal) (interval-end (-> group 'extent)))))) (if have-overlap (let ((merged (merged-variable! group var))) (propagate! (cons merged rest))) variables))))) #(define (finalize! variables) (define (finalize-one! var) (unless (-> var 'final) (set! (-> var 'final) (if (-> var 'tied-to) (begin (finalize-one! (-> var 'tied-to)) (+ (-> var 'tied-to 'final) (-> var 'tied-offset))) (-> var 'ideal))))) (for-each finalize-one! variables)) #(define (solve-lyric-spacing-problem! variables) (fold (lambda (var groups) (propagate! (cons var groups))) '() variables) (finalize! variables)) #(define (respace-lyrics! grob) (let ((elt-array (ly:grob-object grob 'elements #f))) (when elt-array (let* ((elts (ly:grob-array->list elt-array)) (refp (ly:grob-system grob)) (with-iface (lambda (iface) (filter (lambda (g) (grob::has-interface g iface)) elts))) (words (filter (lambda (word) (interval-sane? (ly:grob-extent word word X))) (with-iface 'lyric-syllable-interface))) ;; Includes both LyricHyphen and LyricSpace (constraints (with-iface 'lyric-hyphen-interface)) (variables (map (lambda (word) (let* ((xalign (ly:grob-property word 'self-alignment-X)) (coord (ly:grob-relative-coordinate word refp X)) (orig-ext (ly:grob-extent word word X)) (align-point (interval-index orig-ext xalign)) (ideal (+ coord align-point)) (extent (coord-translate orig-ext (- align-point))) (strength (or (assq-ref (ly:grob-property word 'details) 'strength) 1.0))) (make <lyric-variable> #:ideal ideal #:extent extent #:strength strength))) words)) (word-to-variable (alist->hashq-table (map cons words variables)))) (for-each (lambda (constraint) (let ((added (ly:grob-property constraint 'minimum-distance)) (left-var (hashq-ref word-to-variable (ly:spanner-bound constraint LEFT)))) (when left-var (transform! (-> left-var 'extent) (lambda (e) (cons (interval-start e) (+ (interval-end e) added))))))) constraints) (solve-lyric-spacing-problem! variables) (for-each (lambda (word variable) (let* ((xalign (ly:grob-property word 'self-alignment-X)) (orig-ext (ly:grob-extent word word X)) (align-point (interval-index orig-ext xalign))) (ly:grob-translate-axis! word (- (-> variable 'final) (ly:grob-relative-coordinate word refp X) align-point) X))) words variables))))) \layout { \context { \Lyrics \override LyricText.extra-spacing-width = #'(+inf.0 . -inf.0) \override LyricSpace.springs-and-rods = ##f \override LyricHyphen.springs-and-rods = ##f \override VerticalAxisGroup.after-line-breaking = #respace-lyrics! } }
\version "2.23.81" \include "respace-lyrics.ily" \language "english" struct = { \numericTimeSignature \key bf \major \time 3/4 s2.*5 \break s2.*5 \break } nb = \markup { \small \italic "n.b." } melody = \relative { \clef treble \dynamicUp R2.*4 | d'8\mp ef f4. d8 | d4 \once \phrasingSlurDashed c2_\(^\nb | d8\) ef f4 bf, | c2. | d8\< ef f4. f8 | g8 a bf2\mf | } pianoRH = \relative { \clef treble d''8 ef f4 <f,c'>8 f' | << { g8 a bf2 } \\ { <bf, d>2. } >> | << { ef4 c8 d ef bf } \\ { g2 g4 } >> | << { d'4. ef8 c4 } \\ { <ef, gf>2. } >> | <c' f>4 <f, d'>2 | << { d'4 c } \\ { <ef, gf>2 } >> ef'8 c' | <d, bf'>4 <bf f'>2 | <gf d'>4 c ef8 c' | <d, bf'>4 f c8 f, | << { d'2. } \\ { bf8 a g2 } >> | } pianoLH = \relative { \clef treble bf8 f' ~ f4 a, | g8 d' bf' a g d | \clef bass c,8 g' ef'2 | ef,8 bf' c2 | bf,8 f' bf4 d | ef,8 gf bf c ef4 | bf,8 f' d'4 f | ef,8 c' ef gf ~ gf4 | bf,,8 f' d'4 a | g8 d' bf a g d | } melodyWordsDefault = \lyricmode { Would I know my Sav -- ior %% The higher the details.strength property, the harder the algorithm %% tries to place the lyric syllable close to its ideal position, at the %% expense of other lyric syllables nearby. Try outcommenting this %% override to see the effect. %\once \override LyricText.details.strength = 100 Wrapped in swad -- dling bands, Ly -- ing in a man -- ger bed, Light of hea -- ven ’round His head? } #(set-global-staff-size 19) \paper { ragged-last = ##f ragged-bottom = ##t ragged-right = ##f ragged-last-bottom = ##t tagline = ##f } \layout { \context { \Lyrics \override LyricText.font-size = #0 \override LyricHyphen.font-size = #-0.5 \override LyricHyphen.padding = #0.15 \override LyricHyphen.length = #0.6 %#0.4 \override LyricHyphen.minimum-length = #0.66 \override LyricHyphen.minimum-distance = #1 %0.15 \override LyricHyphen.thickness = 2.0 \override LyricHyphen.dash-period = 8.0 \override LyricExtender.minimum-length = #0 \override LyricExtender.right-padding = #0.5 \override LyricSpace.minimum-distance = #1 \override VerticalAxisGroup.nonstaff-relatedstaff-spacing.padding = #1 } } \score { << \new Staff << \struct \new Voice = "melody" \melody >> \new Lyrics \lyricsto melody \melodyWordsDefault \new PianoStaff << \new Staff = "pianoRH" << \struct \pianoRH >> \new Staff = "pianoLH" << \struct \pianoLH >> >> >> \layout {} }
tricky-lyrics.pdf
Description: Adobe PDF document
OpenPGP_signature
Description: OpenPGP digital signature