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: Dimitris Marinakis
Subject: Re: Custom Spanner with variable length sections
Date: Fri, 15 Apr 2022 23:41:46 +0300

Thank you, much better now :)

The only thing to improve here would be to have separate paddings for each system. For some reason the staff-padding currently behaves more like a fixed offset. If you have very high notes they get clipped. 
customspannerclippednotes.jpg


I'm using 2.23.0. I can't use the latest versions for all of my projects yet because I'm using a font that doesn't have the new microtonal accidentals. I hope the creator of the font finds the time to update it or I'll need to do it myself.
Besides, I have over 12K lines of code with custom stuff that some of it will most likely break with the version change.

I'll do must best to update to the latest version as soon as possible. Thanks again for your help.

Best,
Dimitris

On Fri, Apr 15, 2022 at 10:07 PM Jean Abou Samra <jean@abou-samra.fr> wrote:
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]