Am Do., 31. Jan. 2019 um 10:27 Uhr schrieb Leo Correia de Verdier <leo.correia.de.verd...@gmail.com>: > > Dear List! > > I’m still learning lilypond and working on a piece with long text > instructions that appear over a note or group of notes. So far I have managed > to achieve this by hacking off the stencils from text spanners and replacing > them with the line wrapped markup, but I feel there should be a neater > solution out there that I just haven’t thought or learned about. This hack > has also reached it’s limits because it has created the need for multiple > simultaneous text spanners (not in the example). While I could go further > down the hack road by placing additional spanners in separate voices with > spacer rests or starting to destroy trill spanners too, that would just > increase the mess. Could you please point me towards a tidier path? :) > > Thanks a lot! > > /Leo > > \version "2.19.80" > > longTextInstr = > #(define-music-function (alongtext) > (string?) > #{ > \once \override Staff.TextSpanner.stencil = > #(lambda (grob) > (let* ((grob-X (interval-length > (ly:stencil-extent (ly:line-spanner::print grob) X))) > (layout (ly:grob-layout grob)) > (props (ly:grob-alist-chain grob > (ly:output-def-lookup layout > 'text-font-defaults)))) > > (ly:text-interface::interpret-markup layout props > #{ \markup { > \override #(cons 'line-width grob-X) > \wordwrap-string #alongtext } > #}) > )) > #} > ) > > \relative { > c'1 \longTextInstr "this is a long text instruction to be printed > line-wrapped over a group of notes" > d \startTextSpan > e f > e \stopTextSpan > d > } Hi Leo,
you're code is fine. It does exactly what it is supposed to do. You could consider, making it a tweak and you could use 'grob-interpret-markup' to avoid finding 'layout' 'props' yourself. Though, it's a kown limitation that you can't use more than one TextSpanner per Voice. You already found the usual workaround: Additional Voices. A bold workaround is to define the whole infrastructure for TextSpanners with new/renamed events (class and type), grobs, engravers and start/stop-commands. \version "2.19.82" \include "multiple-spanner-grobs-engravers.ly" \createSpannersAndEngravers #'(X Y Z) \layout { \context { \Global \grobdescriptions #all-grob-descriptions } \context { \Voice \consists \XTextSpannerEngraver \consists \YTextSpannerEngraver \consists \ZTextSpannerEngraver } } #(define (text-spanner->text text) (lambda (grob) (let* ((grob-X (interval-length (ly:stencil-extent (ly:line-spanner::print grob) X)))) (grob-interpret-markup grob #{ \markup \override #(cons 'line-width grob-X) \wordwrap-string #text #})))) txtI = "this is a long text instruction to be printed line-wrapped over a group of notes" txtII = "this is another long text instruction to be printed line-wrapped below a group of notes" \relative { c'1 d -\tweak stencil #(text-spanner->text txtI) \startTextSpan -\tweak stencil #(text-spanner->text txtII) _\x-start e f e \stopTextSpan d \x-stop } HTH, Harm
%% Example for creating multiple custom text spanners %% Based on: % http://lilypond.org/doc/v2.18/input/regression/collated-files.html % look for: % ‘scheme-text-spanner.ly’ \version "2.19.82" #(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)))) #(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))) #(define (axis-offset-symbol axis) (if (eqv? axis X) 'X-offset 'Y-offset)) #(define (set-axis! grob axis) (if (not (number? (ly:grob-property grob 'side-axis))) (begin (set! (ly:grob-property grob 'side-axis) axis) (ly:grob-chain-callback grob (if (eqv? axis X) ly:side-position-interface::x-aligned-side side-position-interface::y-aligned-side) (axis-offset-symbol axis))))) #(define (scheme-event-spanner-types-proc class-lst type-lst) (map (lambda (x y) (cons x (list '(description . "Used to signal where scheme text spanner brackets start and stop.") (cons 'types (list 'post-event y 'span-event 'event))))) class-lst type-lst)) #(define scheme-event-spanner-type (lambda (x) (set-object-property! (car x) 'music-description (cdr (assq 'description (cdr x)))) (let ((lst (cdr x))) (set! lst (assoc-set! lst 'name (car x))) (set! lst (assq-remove! lst 'description)) (hashq-set! music-name-to-property-table (car x) lst) (cons (car x) lst)))) #(define spanner-props `( (bound-details . ((left . ((Y . 0) (padding . 0.25) (attach-dir . ,LEFT) )) (left-broken . ((end-on-note . #t))) (right . ((Y . 0) (padding . 0.25) )) )) (dash-fraction . 0.2) (dash-period . 3.0) (direction . ,UP) (font-shape . italic) (left-bound-info . ,ly:line-spanner::calc-left-bound-info) (outside-staff-priority . 350) (right-bound-info . ,ly:line-spanner::calc-right-bound-info) (staff-padding . 0.8) (stencil . ,ly:line-spanner::print) (style . dashed-line) (meta . ((class . Spanner) (interfaces . (font-interface line-interface line-spanner-interface outside-staff-interface side-position-interface)))))) #(define define-engraver (lambda (engr-name g-name event-types) (module-define! (current-module) engr-name (lambda (context) (let ((span '()) (finished '()) (ev-direction '()) (event-start '()) (event-stop '())) `((listeners (,event-types . ,(lambda (engraver event) (if (= START (ly:event-property event 'span-direction)) (begin (set! event-start event) (set! ev-direction (ly:event-property event 'direction 1))) (set! event-stop event))))) (acknowledgers (note-column-interface . ,(lambda (engraver grob source-engraver) (if (ly:spanner? span) (begin (ly:pointer-group-interface::add-grob span 'note-columns grob) (add-bound-item span grob))) (if (ly:spanner? finished) (begin (ly:pointer-group-interface::add-grob finished 'note-columns grob) (add-bound-item finished grob)))))) (process-music . ,(lambda (trans) (if (ly:stream-event? event-stop) (if (null? span) (ly:warning "You're trying to end a scheme text spanner but you haven't started one.") (begin (set! finished span) (ly:engraver-announce-end-grob trans finished event-start) (set! span '()) (set! event-stop '())))) (if (ly:stream-event? event-start) (begin (set! span (ly:engraver-make-grob trans g-name event-start)) (ly:grob-set-property! span 'direction ev-direction) (set-axis! span Y) (set! event-start '()))))) (stop-translation-timestep . ,(lambda (trans) (if (and (ly:spanner? span) (null? (ly:spanner-bound span LEFT))) (ly:spanner-set-bound! span LEFT (ly:context-property context 'currentMusicalColumn))) (if (ly:spanner? finished) (begin (if (null? (ly:spanner-bound finished RIGHT)) (ly:spanner-set-bound! finished RIGHT (ly:context-property context 'currentMusicalColumn))) (set! finished '()) (set! event-start '()) (set! event-stop '()))))) (finalize . ,(lambda (trans) (if (ly:spanner? finished) (begin (if (null? (ly:spanner-bound finished RIGHT)) (ly:spanner-set-bound! finished RIGHT (ly:context-property context 'currentMusicalColumn))) (set! finished '()))) (if (ly:spanner? span) (begin (ly:warning "I think there's a dangling scheme text spanner :-)") (ly:grob-suicide! span) (set! span '()))))))))))) createSpannersAndEngravers = #(define-void-function (name-list)(list?) (let* ((naming-lst (map (lambda (name) (list (string->symbol (format #f "~aTextSpanEvent" name)) (ly:camel-case->lisp-identifier (string->symbol (format #f "~aTextSpanEvent" name))) (string->symbol (format #f "~aTextSpanner" name)) (string->symbol (format #f "~aTextSpannerEngraver" name)) (string->symbol (string-downcase (format #f "~a-start" name))) (string->symbol (string-downcase (format #f "~a-stop" name))))) name-list)) (new-scheme-event-spanner-types (scheme-event-spanner-types-proc (map car naming-lst) (map second naming-lst))) (scheme-event-spanner-types (map scheme-event-spanner-type new-scheme-event-spanner-types))) (set! music-descriptions (append scheme-event-spanner-types music-descriptions)) (set! music-descriptions (sort music-descriptions alist<?)) (for-each (lambda (evt text-span-event-name g-name engr-name cmd-start cmd-stop) (define-event-class text-span-event-name 'span-event) (add-grob-definition g-name spanner-props) (define-engraver engr-name g-name text-span-event-name) (module-define! (current-module) cmd-start (make-span-event evt START)) (module-define! (current-module) cmd-stop (make-span-event evt STOP))) (map car naming-lst) (map second naming-lst) (map third naming-lst) (map fourth naming-lst) (map fifth naming-lst) (map sixth naming-lst)) (newline) (pretty-print (cons "The following events (class and type), grobs, engravers and start/stop-commands are created" naming-lst)))) %%%%%%%%%%%%%%%%%%%%%%% %% Example %%%%%%%%%%%%%%%%%%%%%%% %{ %% rename path to your file, which holds the engraver code % \include "multiple-spanner-grobs-engravers.ly" \createSpannersAndEngravers #'(Hallo Welt A) \layout { \context { \Global \grobdescriptions #all-grob-descriptions } \context { \Voice \consists \HalloTextSpannerEngraver \consists \WeltTextSpannerEngraver \consists \ATextSpannerEngraver } } \relative { \override HalloTextSpanner.to-barline = ##f \override HalloTextSpanner.bound-details.left.text = #"hall" \override HalloTextSpanner.bound-details.right.text = #"lo" \override WeltTextSpanner.bound-details.left.text = #"we" \override WeltTextSpanner.bound-details.right.text = #"lt" \override ATextSpanner.bound-details.left.text = #"A" \override ATextSpanner.bound-details.right.text = #"A" a4 b\hallo-start c\welt-start d a4 b c d a4 \a-start b c\hallo-stop d \welt-stop \break a4\hallo-start \welt-start b d \a-stop c a4 b\hallo-stop c d \welt-stop a1 } %}
_______________________________________________ lilypond-user mailing list lilypond-user@gnu.org https://lists.gnu.org/mailman/listinfo/lilypond-user