Hi David, hi Mike, hi all,I sometimes (still use Lilypond, and when I do, I typically use) your wonderful snippet for snapping "close" syllables into a single token.
(For those who don't know this contribution: it's meant to get rid of unpleasant thin-space gaps between syllables in cramped conditions, when LyricHyphens are not shown, but there's a "kerning" problem due to the fact that syllables are engraved as separate entities.)
Now I stumbled across an issue with that: two syllables are combined which are clearly far apart; see the attached screenshot and reduced working example from an actual engraving. It's not exactly minimal, but at least it's reasonably short and shows the problem. And the exact circumstances when the bug occurs are not clear to me, and it's somewhat sensitive to reduction.
According to my tests, it's related to the facts that there is(1) a hyphen *before* the syllable "bor" in "ver -- bor -- gen" in the alto part;
(2) a line break exactly there (moving the break "remedies" the problem); and
(3) another voice/staff simultaneously (commenting out the soprano mitigates the issue as well).
It's not, however, related to the accidental in the soprano, or the fact that the whole note in the lower voice is wider than the quarter in the upper voice (replacing d'1 by d'4*4 doesn't help).
Any thoughts on what goes wrong here, and about a possible fix or workarounds?
[ In case you wonder: it's an excerpt of Heinrich Schütz' "Die Himmel erzählen", converted from James Gibb's contribution 46745 to CPDL (engraved in Capella) via musicxml2ly. I mainly want to fix a couple of minor spelling errors; the conversion worked fairly pleasant in general. ]
Cheers, Alex
\version "2.18.0" % absolutely necessary! \header { snippet-title = "Magnetic snapping lyric syllables" snippet-author = "David Nalesnik and Mike Solomon," snippet-source = "http://lists.gnu.org/archive/html/lilypond-user/2014-03/msg00489.html" snippet-description = \markup { This snippet handles lyric syllables that belong to one word together and ensures that there are no irritating gaps between them (solves issue 2458). } % add comma-separated tags to make searching more effective: tags = "lyrics, syllable, gap, hyphen" % is this snippet ready? See meta/status-values.md status = "undecided" } %%%%%%%%%%%%%%%%%%%%%%%%%% % here goes the snippet: % %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% ADD NEW GROB INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #(ly:add-interface 'lyric-word-interface "A word of lyrics. Includes syllables and hyphens." '(text-items)) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% CREATE NEW GROB PROPERTY %%%%%%%%%%%%%%%%%%%%%%%%%%%% #(define (define-grob-property symbol type? description) (if (not (equal? (object-property symbol 'backend-doc) #f)) (ly:error (_ "symbol ~S redefined") symbol)) (set-object-property! symbol 'backend-type? type?) (set-object-property! symbol 'backend-doc description) symbol) #(map (lambda (x) (apply define-grob-property x)) `( (text-items ,list? "Syllables and hyphens of a word of lyrics"))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% ADD DEFINITION OF GROB %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #(define (add-grob-definition grob-name grob-entry) (let* ((meta-entry (assoc-get 'meta grob-entry)) (class (assoc-get 'class meta-entry)) (ifaces-entry (assoc-get 'interfaces meta-entry))) (set-object-property! grob-name 'translation-type? ly:grob-properties?) (set-object-property! grob-name 'is-grob? #t) (set! ifaces-entry (append (case class ((Item) '(item-interface)) ((Spanner) '(spanner-interface)) ((Paper_column) '((item-interface paper-column-interface))) ((System) '((system-interface spanner-interface))) (else '(unknown-interface))) ifaces-entry)) (set! ifaces-entry (uniq-list (sort ifaces-entry symbol<?))) (set! ifaces-entry (cons 'grob-interface ifaces-entry)) (set! meta-entry (assoc-set! meta-entry 'name grob-name)) (set! meta-entry (assoc-set! meta-entry 'interfaces ifaces-entry)) (set! grob-entry (assoc-set! grob-entry 'meta meta-entry)) (set! all-grob-descriptions (cons (cons grob-name grob-entry) all-grob-descriptions)))) #(add-grob-definition 'LyricWord `(;(stencil . ,ly:lyric-word::print) (meta . ((class . Spanner) (interfaces . (lyric-hyphen-interface lyric-word-interface text-interface)))))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #(define (add-bound-item spanner item) (if (null? (ly:spanner-bound spanner LEFT)) (ly:spanner-set-bound! spanner LEFT item) (ly:spanner-set-bound! spanner RIGHT item))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ENGRAVER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% collectlyricwordEngraver = % Collect lyric syllables and hyphens into words. (LyricExtender?) % The bounds of a LyricWord should be LyricText grobs, when available. % When a LyricWord consists of a single syllable, the left and right bounds % should be the same grob. % When a spanner is broken, the ends not attached to LyricText grobs should % attach to NonMusicalPaperColumn, as with any spanner. #(lambda (context) (let ((word-bits '()) ; holds syllables and hyphens (word '()) ; LyricWord grob we're building (collect #f)) ; signal to end word and begin another (make-engraver (acknowledgers ((lyric-syllable-interface engraver grob source-engraver) (set! collect #t) (set! word-bits (append word-bits (list grob))) (if (ly:grob? word) (add-bound-item word grob))) ((lyric-hyphen-interface engraver grob source-engraver) (let* ((props (ly:grob-basic-properties grob)) (meta (assoc-get 'meta props)) (name (assoc-get 'name meta))) ; don't collect LyricSpace ; use it as our signal to end o word/start a new one (if (eq? name 'LyricSpace) (set! collect #f) (set! word-bits (append word-bits (list grob))))))) ((process-music trans) (if (and collect (pair? word-bits)) (begin (if (not (ly:grob? word)) (set! word (ly:engraver-make-grob trans 'LyricWord '()))) ; car should always be a LyricText grob, but maybe a check is in order (add-bound-item word (car word-bits)) (for-each (lambda (x) (ly:pointer-group-interface::add-grob word 'text-items x)) word-bits))) (if (not collect) (begin (if (ly:grob? word) (begin (if (pair? word-bits) (begin (for-each (lambda (x) (ly:pointer-group-interface::add-grob word 'text-items x)) word-bits) (if (null? (ly:spanner-bound word RIGHT)) (ly:spanner-set-bound! word RIGHT (car word-bits))))) (set! word (ly:engraver-make-grob trans 'LyricWord '())) (set! collect #t))) (if (not (ly:grob? word)) (begin (set! word (ly:engraver-make-grob trans 'LyricWord '())) (if (pair? word-bits) (begin (ly:spanner-set-bound! word LEFT (car word-bits)) (for-each (lambda (x) (ly:pointer-group-interface::add-grob word 'text-items x)) word-bits) (if (null? (ly:spanner-bound word RIGHT)) (ly:spanner-set-bound! word RIGHT (car word-bits))))) (set! word '()))))) (set! word-bits '()))))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #(define (compress-pair syl-a hyphen syl-b threshold) (let* ((hyphen-sten (ly:lyric-hyphen::print hyphen)) (hyphen-ex (if (ly:stencil? hyphen-sten) (ly:stencil-extent hyphen-sten X) (cons (/ threshold -2) (/ threshold 2))))) (if (> (interval-length hyphen-ex) threshold) '() ; no compression--DO NOTHING! (let* ((syl-a-text (ly:grob-property syl-a 'text)) (syl-a-text (if (markup? syl-a-text) syl-a-text (markup syl-a-text))) (syl-b-text (ly:grob-property syl-b 'text)) (syl-b-text (if (markup? syl-b-text) syl-b-text (markup syl-b-text))) (full-text (make-concat-markup (list syl-a-text syl-b-text)))) (set! (ly:grob-property syl-a 'text) full-text) (set! (ly:grob-property syl-b 'text) empty-markup) (set! (ly:grob-property syl-a 'stencil) lyric-text::print) (set! (ly:grob-property syl-b 'stencil) lyric-text::print) (set! (ly:grob-property hyphen 'stencil) empty-stencil))))) #(define (lyric-word-compressor threshold) (lambda (grob) ; LyricWord (let* ((items (ly:grob-object grob 'text-items)) (item-list (ly:grob-array->list items))) (if (> (length item-list) 1) ; do nothing to monosyllabic words (let* ((text-grobs (filter (lambda (item) (grob::has-interface item 'lyric-syllable-interface)) item-list)) (hyphen-grobs (filter (lambda (item) (grob::has-interface item 'lyric-hyphen-interface)) item-list))) (define (helper seed tx-list hy-list) (if (and (pair? (cdr tx-list)) (pair? hy-list)) (let ((next-syl (cadr tx-list)) (hyphen (car hy-list))) (compress-pair seed hyphen next-syl threshold) (if (equal? empty-markup (ly:grob-property next-syl 'text)) (helper seed (cdr tx-list) (cdr hy-list)) (helper (cadr tx-list) (cdr tx-list) (cdr hy-list)))))) (helper (car text-grobs) text-grobs hyphen-grobs)))))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EXAMPLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% SOME OTHER FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #(define (dim-hack grob ax) (let* ((elts (ly:grob-object grob 'text-items)) (common (ly:grob-common-refpoint-of-array grob elts ax)) (rel (ly:relative-group-extent elts common ax)) (off (ly:grob-relative-coordinate grob common ax))) (coord-translate rel (- off)))) #(define (height-hack grob) (dim-hack grob Y)) #(define (width-hack grob) (dim-hack grob X)) #(define (ly:lyric-word::underline grob) (let* ((height (height-hack grob)) (width (width-hack grob))) (make-line-stencil 0.1 (car width) 0 (cdr width) 0))) #(define (ly:lyric-word::boxer grob) (let* ((yext (height-hack grob)) (xext (width-hack grob)) (thick 0.1)) (ly:stencil-add (make-filled-box-stencil xext (cons (- (car yext) thick) (car yext))) (make-filled-box-stencil xext (cons (cdr yext) (+ (cdr yext) thick))) (make-filled-box-stencil (cons (cdr xext) (+ (cdr xext) thick)) yext) (make-filled-box-stencil (cons (- (car xext) thick) (car xext)) yext)))) wordunderline = \once \override LyricWord.stencil = #ly:lyric-word::underline wordbox = \once \override LyricWord.stencil = #ly:lyric-word::boxer %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \layout { \context { \Global \grobdescriptions #all-grob-descriptions } \context { \Lyrics \consists \collectlyricwordEngraver } }
\version "2.19.82" \include "lyric-syllable-magnetic-snap.ily" \layout { \context { \Lyrics \override LyricExtender.minimum-length = #1 \override LyricWord.after-line-breaking = #(lyric-word-compressor 0.5) \override LyricHyphen.minimum-distance = #0.5 } } #(set-global-staff-size 16) sopI = { e'4 d'4 g'4 f'4 | % 87 e'4 d'8 d'8 cis'4( d'4 ~ | % 88 d'4 cis'4) d'2 | % 89 r4 d''4 c''4 f''4 ~ | % 90 f''8 es''8 d''8 c''8 bes'4 c''4 | % 91 fis'4 ( bes'4 a'2 ) | % 92 g'1 \breathe | % 93 d''2 e''2 | % 94 f''4 d''4 c''4 c''8 c''8 | % 95 es''2 d''2 | % 96 d''1 | % 97 } lyrSI = \lyricmode { wie -- der an das -- sel -- bi -- ge En -- de, und bleibt nichts vor ih -- rer Hitz ver -- bor -- gen. Die Him -- mel er -- zäh -- len die Eh -- re Got } alt = { g2 g2 | % 87 g2 a4 bes8 bes8 | % 88 a2 d'2 \breathe | % 89 d'4 bes4 f'4. es'8 | % 90 d'8 c'8 bes4 es'2 | % 91 d'1 | % 92 g1 \breathe | % 93 g'2 g'2 | % 94 f'4 f'4 f'4 f'8 f'8 | % 95 g'2. g'4 | % 96 fis'1 | % 97 } lyrA = \lyricmode { der an das -- sel -- bi -- ge En -- de, % This shows the bug: und bleibt nichts vor ih -- rer Hitz ver -- bor -- gen. % This works fine (alignment-wise): % und bleibt nichts vor ih -- rer Hitz ver bor -- gen. Die Him -- mel er -- zäh -- len die Eh -- re Got } \score { \new ChoirStaff << \new Staff << \new Voice = "sopI" \sopI >> \new Lyrics \lyricsto "sopI" \lyrSI \new Staff << \new Voice = "alt" \alt >> \new Lyrics \lyricsto "alt" \lyrA >> \layout { \context { \Lyrics \override LyricWord.stencil = #ly:lyric-word::boxer } } }
smime.p7s
Description: S/MIME Cryptographic Signature
_______________________________________________ lilypond-user mailing list lilypond-user@gnu.org https://lists.gnu.org/mailman/listinfo/lilypond-user