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: Thu, 14 Apr 2022 18:44:36 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.7.0

Le 14/04/2022 à 16:09, Kieren MacMillan a écrit :
Hi Dimitris,

I need a custom spanner that has x sections with different symbols. This is 
going to be tricky so any help is appreciated.
https://github.com/davidnalesnik/lilypond-text-spanner-inner-texts


Ok, I'm not sure if this solves the problem fully, so I'll post what I had
started writing anyway:


\version "2.22.2"

#(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))
          (siblings (ly:spanner-broken-into orig))
          (sib-widths
           (map (lambda (sib)
                  (let ((sys (ly:grob-system sib))
                        (left (ly:spanner-bound sib LEFT))
                        (right (ly:spanner-bound sib RIGHT)))
                    (- (ly:grob-relative-coordinate right sys X)
                       (ly:grob-relative-coordinate left sys X))))
                siblings))
          (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.
           (if (eq? grob (car siblings))
              (set! retval sib-stil)
              (ly:grob-set-property! (car siblings)
                                     'stencil
                                     sib-stil))
           (set! sib-changes (cdr sib-changes))
           (set! siblings (cdr siblings))
           (set! sib-stil empty-stencil))
          ((> 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
   `((direction . ,DOWN)
     (normalized-endpoints . ,ly:spanner::calc-normalized-endpoints)
     (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))))))

#(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))))))
      ((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)

{
  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 1\stopFiller
}


Jean





reply via email to

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