Hi all,

I'm experimenting with a way to support multiple text spanners in a single
voice, using the 'spanner-id property which already enables multiple slurs
and phrasing slurs.

The attached code works--until the spanners cross a line break.  Then the
order of the spanners is reversed.  I've experimented with different start
and end points of the spanners, reversing lists within the engraver--no
change.

The second example shows that tweaks can mess up this reversed ordering
even further.

Does anyone know what is happening here?  I really have no idea :(

Thanks,

David
\version "2.19"

%% Based on the rewrite of Text_spanner_engraver in
%% input/regression/scheme-text-spanner.ly

#(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 (eq? 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 (eq? axis X)
             ly:side-position-interface::x-aligned-side
             side-position-interface::y-aligned-side)
         (axis-offset-symbol axis)))))

schemeTextSpannerEngraver =
#(lambda (context)
   (let ((span '()) ; list of started spanner
          (finished '()) ; list of spanners in completion stage
          (event-start '()) ; list of START events
          (event-stop '())) ; list of STOP events
     (make-engraver
      ;; \startTextSpan, \stopTextSpan, and the like create events
      ;; which we collect here.
      (listeners
       ((text-span-event engraver event)
        (if (= START (ly:event-property event 'span-direction))
            (set! event-start (cons event event-start))
            (set! event-stop (cons event event-stop)))))
      ;; Populate 'note-columns property of spanners.  Bounds are
      ;; set to note columns, and each spanner keeps a record of
      ;; the note columns it traverses.
      (acknowledgers
       ((note-column-interface engraver grob source-engraver)
        (for-each (lambda (s)
                    (ly:pointer-group-interface::add-grob
                     s 'note-columns grob)
                    (add-bound-item s grob))
          span)
        (for-each (lambda (f)
                    (ly:pointer-group-interface::add-grob
                     f 'note-columns grob)
                    (add-bound-item f grob))
          finished)))

      ((process-music trans)
       ;; Move begun spanners from 'span' to 'finished'.  We do this
       ;; on the basis of 'spanner-id.  If we find a match--either
       ;; the strings are the same, or both are unset--a transfer
       ;; can be made.  Return a warning if we find no match: spanner
       ;; hasn't been properly begun.
       (for-each
        (lambda (es)
          (let ((es-id (ly:event-property es 'spanner-id)))
            (let loop ((sp span))
              (let ((sp-id (ly:event-property
                            (event-cause (car sp)) 'spanner-id)))
                (cond
                 ((null? sp) (ly:warning "No spanner to end!!"))
                 ((and
                   (string? sp-id)
                   (string? es-id)
                   (string=? sp-id es-id))
                  (set! finished (cons (car sp) finished))
                  (set! span (remove (lambda (s) (eq? s (car sp))) span)))
                 ((and
                   (null? sp-id)
                   (null? es-id))
                  (set! finished (cons (car sp) finished))
                  (set! span (remove (lambda (s) (eq? s (car sp))) span)))
                 (else (loop (cdr sp))))))))
        event-stop)

       ;; The end of our spanners can be acknowledged by other engravers.
       (for-each
        (lambda (f)
          (ly:engraver-announce-end-grob trans f (event-cause f)))
        finished)

       ;; Make spanners called for by START events.
       (for-each
        (lambda (es)
          (set! span
                (cons
                 (ly:engraver-make-grob trans 'TextSpanner es)
                 span))
          (set-axis! (car span) Y))
        event-start)

       ;; Events have served their purpose for this timestep.  Clear
       ;; the way for new events in later timesteps.
       (set! event-start '())
       (set! event-stop '()))

      ((stop-translation-timestep trans)
       ;; Set bounds of spanners to PaperColumns if they haven't been set.
       ;; This allows spanners to be drawn between spacers.  Other uses?
       ;; Doesn't appear to affect whether spanners can de drawn between
       ;; rests.
       (for-each
        (lambda (s)
          (if (null? (ly:spanner-bound s LEFT))
              (ly:spanner-set-bound! s LEFT
                (ly:context-property context 'currentMusicalColumn))))
        span)

       (for-each
        (lambda (f)
          (if (null? (ly:spanner-bound f RIGHT))
              (ly:spanner-set-bound! f RIGHT
                (ly:context-property context 'currentMusicalColumn))))
        finished)

       (set! finished '()))

      ((finalize trans)
       ;; If spanner ends on spacer at end of context?
       (for-each
        (lambda (f)
          (if (null? (ly:spanner-bound f RIGHT))
              (ly:spanner-set-bound! f RIGHT
                (ly:context-property context 'currentMusicalColumn))))
        finished)
       (set! finished '())
       ;; User didn't end spanner.
       (for-each
        (lambda (sp)
          (ly:warning "incomplete spanner removed!")
          (ly:grob-suicide! sp))
        span)
       (set! span '())))))

startTextSpanOne =
#(make-music 'TextSpanEvent 'span-direction START 'spanner-id "1")

stopTextSpanOne =
#(make-music 'TextSpanEvent 'span-direction STOP 'spanner-id "1")

startTextSpanTwo =
#(make-music 'TextSpanEvent 'span-direction START 'spanner-id "2")

stopTextSpanTwo =
#(make-music 'TextSpanEvent 'span-direction STOP 'spanner-id "2")

startTextSpanThree =
#(make-music 'TextSpanEvent 'span-direction START 'spanner-id "3")

stopTextSpanThree =
#(make-music 'TextSpanEvent 'span-direction STOP 'spanner-id "3")

%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EXAMPLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\layout {
  \context {
    \Voice
    \remove #"Text_spanner_engraver"
    \consists \schemeTextSpannerEngraver
  }
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Order of lines is reversed at line break.
\relative c' {
  \override TextSpanner.outside-staff-padding = 2
  \override TextSpanner.thickness = 4
  \override TextSpanner.style = ##f
  a4
  -\tweak color #red \startTextSpan
  b c
  -\tweak color #darkred \startTextSpanOne
  d

  a4
  -\tweak color #magenta \startTextSpanTwo
  b c
  -\tweak style #'zigzag \startTextSpanThree
  d
  \break %% ugh--order not preserved!

  a4 b c d\stopTextSpanThree
  a4 b\stopTextSpanTwo
  c d\stopTextSpanOne
  a4 b c d\stopTextSpan
}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Order is again reversed.  Commenting tweaks in and out
%% modifies order in strange ways.
\relative c' {
  \override TextSpanner.outside-staff-padding = 2
  a4
  -\tweak style ##f
  -\tweak color #red
  -\tweak thickness 4
  \startTextSpan
  b c
  -\tweak style ##f
  -\tweak color #darkred
  -\tweak thickness 4
  \startTextSpanOne
  d

  a4
  -\tweak style ##f
  -\tweak color #magenta
  -\tweak thickness 4
  \startTextSpanTwo
  b c
  -\tweak style #'zigzag
  %% Comment in and watch position of zigzag line!
  %-\tweak thickness 4
  \startTextSpanThree
  d

  \break %% ugh--order not preserved!

  a4 b c d\stopTextSpan

  a4 b\stopTextSpanOne
  c d\stopTextSpanTwo

  a4 b c d\stopTextSpanThree
}
_______________________________________________
lilypond-devel mailing list
lilypond-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-devel

Reply via email to