lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Custom Spanner with variable length sections


From: Jean Abou Samra
Subject: Re: Custom Spanner with variable length sections
Date: Fri, 15 Apr 2022 21:07:53 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.7.0

Le 15/04/2022 à 16:35, Dimitris Marinakis a écrit :
Thank you so much Jean. That looks amazing for a first try. Great work!

Sorry it took me a while to test it.


Less than a day? I didn't find that long :-)


Upon testing I found out that it only works when there are line breaks (not on a single system).



Right, I intended to add this code, but forgot it along the way.



Also it would be useful to have control over the broken.left/right paddings. This is an issue especially if the spanner is used above the stave (clef collisions).

Is it too hard to an option to align the broken sections on the first and last notes in the system instead of the system edges? The current behaviour isn't wrong. It's just not suitable for all use cases.



See the second attempt below. This SymbolFiller implements a subset
of the horizontal-line-spanner-interface. You can override padding and
end-on-note in bound-details.

I hope you don't mind that this now requires a development version.

Best,
Jean



\version "2.23.7"

#(define (define-grob! grob-name grob-entry)
   (set! all-grob-descriptions
         (cons ((@@ (lily) completize-grob-entry)
                (cons grob-name grob-entry))
               all-grob-descriptions)))

#(define (partial-sums lst)
   (cdr (reverse! (fold
                   (lambda (new previous)
                     (cons (+ new (car previous))
                           previous))
                   (list 0)
                   lst))))

#(define (symbol-filler::print grob)
   (let* ((widths (ly:grob-property grob 'widths))
          (symbols (ly:grob-property grob 'symbols))
          (orig (ly:grob-original grob))
          (proto-siblings (ly:spanner-broken-into orig))
          (siblings (if (null? proto-siblings)
                        (list grob)
                        proto-siblings))
          (sib-exts-to-fill
           (map
            (lambda (sib)
              (define (on-dir sym dir)
                (let* ((details (ly:grob-property sib sym))
                       (x (assoc-get 'X details))
                       (pding (assoc-get 'padding details)))
                  (+ x (* -1 dir pding))))
              (cons (on-dir 'left-bound-info LEFT)
                    (on-dir 'right-bound-info RIGHT)))
            siblings))
          (sib-widths (map interval-length sib-exts-to-fill))
          (sib-changes (partial-sums sib-widths))
          (total-spanner-width (apply + sib-widths))
          (total-sym-width (apply + widths))
          (normalized-syms (map (lambda (x)
                               (* x (/ total-spanner-width total-sym-width)))
                             widths))
          (sym-changes (partial-sums normalized-syms))
          (sib-stil empty-stencil)
          (len-so-far 0)
          (retval #f))
     ;; Let's do an exception.  This is easier written in imperative style.
     (while (and (pair? sib-changes)
                 (pair? sym-changes))
       (let* ((sib (car siblings))
              (next-sym-maybe (car symbols))
              (next-stil-maybe (grob-interpret-markup sib next-sym-maybe))
              (len (interval-length (ly:stencil-extent next-stil-maybe X)))
              (new-len-so-far (+ len len-so-far)))
         (cond
          ((> new-len-so-far (car sib-changes))
           ;; Used full length of this broken piece.  Set
           ;; its stencil and start using the next.
           (let* ((tr (- (interval-start (car sib-exts-to-fill))
                         (ly:grob-relative-coordinate sib
(ly:grob-system sib)
                                                      X)))
                  (tr-stil (ly:stencil-translate-axis sib-stil tr X)))
             (if (eq? grob (car siblings))
                 (set! retval tr-stil)
                 (ly:grob-set-property! (car siblings)
                                        'stencil
                                        tr-stil)))
           (set! sib-changes (cdr sib-changes))
           (set! siblings (cdr siblings))
           (set! sib-stil empty-stencil)
           (set! sib-exts-to-fill (cdr sib-exts-to-fill)))
          ((> new-len-so-far (car sym-changes))
           ;; Done with this symbol, start using the next.
           (set! sym-changes (cdr sym-changes))
           (set! symbols (cdr symbols)))
          (else
           (set! sib-stil (ly:stencil-stack sib-stil X RIGHT next-stil-maybe 0))
           (set! len-so-far new-len-so-far)))))
     retval))

#(define-grob! 'SymbolFiller
   `((bound-details . ((left . ((padding . 0)))
                       (right . ((padding . 0)))))
     (direction . ,DOWN)
     (left-bound-info . ,ly:horizontal-line-spanner::calc-left-bound-info)
     (normalized-endpoints . ,ly:spanner::calc-normalized-endpoints)
     (right-bound-info . ,ly:horizontal-line-spanner::calc-right-bound-info)
     (stencil . ,symbol-filler::print)
     (staff-padding . 3.0)
     (symbols . ,(grob::calc-property-by-copy 'symbols))
     (widths . ,(grob::calc-property-by-copy 'widths))
     (Y-offset . ,ly:side-position-interface::y-aligned-side)
     (meta . ((class . Spanner)
              (interfaces . (side-position-interface
horizontal-line-spanner-interface))))))

#(define (Symbol_filler_engraver context)
   (let ((filler #f)
         (ev #f))
     (make-engraver
      (listeners
       ((symbol-filler-event engraver event)
        (set! ev event)))
      ((process-music engraver)
       (if ev
           (let ((d (ly:event-property ev 'span-direction)))
             (if (eqv? d LEFT)
                 (begin
                  (set! filler (ly:engraver-make-grob engraver 'SymbolFiller ev))
                  (ly:spanner-set-bound! filler
                                         LEFT
                                         (ly:context-property context 'currentMusicalColumn)))
                 (begin
                  (ly:spanner-set-bound! filler
                                         RIGHT
                                         (ly:context-property context 'currentMusicalColumn))
                  (ly:engraver-announce-end-grob engraver filler ev))))))
      (acknowledgers
       ((note-column-interface engraver grob source-engraver)
        (when filler
          (ly:pointer-group-interface::add-grob filler 'note-columns grob))))
      ((stop-translation-timestep engraver)
       (set! ev #f)))))

\layout {
  \context {
    \Global
    \grobdescriptions #all-grob-descriptions
  }
  \context {
    \Voice
    \consists #Symbol_filler_engraver
  }
}

#(define (define-event! type properties)
   (set! properties (assoc-set! properties 'name type))
   (hashq-set! music-name-to-property-table type properties)
   (set! music-descriptions
         (sort (cons (cons type properties)
                     music-descriptions)
               alist<?)))

#(define-event-class 'symbol-filler-event 'span-event)

#(define-event! 'SymbolFillerEvent
   '((types . (symbol-filler-event span-event post-event event))))

startFiller =
#(define-music-function (widths symbols) (number-list? markup-list?)
   (make-music 'SymbolFillerEvent
               'span-direction LEFT
               'widths widths
               'symbols symbols))

stopFiller = #(make-music 'SymbolFillerEvent 'span-direction RIGHT)

{
  % Demonstrate various settings.
  \override SymbolFiller.bound-details.left.padding = 2
  \override SymbolFiller.bound-details.left-broken.padding = 1
  \override SymbolFiller.bound-details.left-broken.end-on-note = ##t
  \override SymbolFiller.bound-details.right.padding = 2
  \override SymbolFiller.bound-details.right-broken.padding = 1
  \override SymbolFiller.bound-details.right-broken.end-on-note = ##t
  g'1\startFiller #'(10 20) \markuplist { a b } 1\stopFiller
  \break
  g'1\startFiller #'(5 5 10 30 50) \markuplist { * \musicglyph "scripts.trill_element" + × "#" }
   1 1 1 1
  \break
  1 1 1 1 1
  1 1 1 1 1
  \break
  1 1 1 1 1
  \break
  1 1 1 1 1
  \break
  1 1\stopFiller
}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]