Am Fr., 25. Okt. 2019 um 22:41 Uhr schrieb Leo Correia de Verdier

> I realized that defining the glissando gradient taking the stencil as point 
> of departure, rather than the grob properties, makes it work for broken 
> glissandi too. It was a quite minimal change.
> (gliss-gradient (/ (+ (- (car stil-y-ext) (cdr stil-y-ext)) (* 
> half-line-thick 2))
>                                 (+ (- (car stil-x-ext) (cdr stil-x-ext)) (* 
> half-line-thick 2))
>                                 (if (> Y-right Y-left) 1 -1)))

Yep, meanwhile I did similar, but relying on the actual
Glissando.thickness. `half-line-thick´ is derived from
staff-symbol-line-thickness, which may result in fishy calculations
for changed thicknesses.

Attached you'll find a heavily revised code, i.e. a lot clean-up, more
respect for user-settings, a plethora of inline comments.
Some TODOs inline, the more severe are displayed.

Atm, I don't know what could be done further with this approach (apart
from said TODOs).
Thus I'll wait for feature-requests and bug-reports ;)

I said "this approach", because technically speaking it's an override,
nothing more.
One could think of others:
(1) Code an engraver to keep track of all those affected items more
easily. NB currently other items like Fingerings, StringNumbers etc
are not included. And what about other spanners like Hairpins or
TextSpanners. Currently not even tested.
(2) Create a new Grob. We already have StemStub and StemTremolo. I
could imagine something like GlisandoStem. Ofcourse this would be the
most general approach. But also the probably most involved and
complicated one.

For now I have to stop thinking/coding, though.
And monday my autumn-break ends :((

\version "2.19.83"

#(define (glissando-and-stems pad-y)
"Returns the unchanged @code{Glissando}, @code{Stem}s from passed 
@code{NoteColumn}s are recreated to start at the glissando-line.  @var{pad-y}
may provide a padding-value.
A @code{Flag} is moved to fit again with the new @code{Stem}.
@code{Script} at a passed @code{NoteColumn} is moved to sit at note-head-side.
Inner @code{Beam}s are recreated to be parallel to the glissando-line, unless
an override for @code{Beam.details.beamed-glissando-stem-positions}, expecting
a number-pair, is suppled.  This is prefered.
;; Glissando has no pointer to the covered NoteColumns, because in most 
;; traditional music NoteColumns are *not* skipped.
;; Thus reading those NoteColumns is inconvenient...
  (lambda (grob)
    (let ((gliss-stil (ly:grob-property grob 'stencil)))
      (if (not (ly:stencil? gliss-stil))
          ;; Warn if no Glissando.stencil is present, do nothing else
          (ly:warning "No stencil for ~a found, stopping here" grob)
          (let* (
                 ;; some generals
                 (sys (ly:grob-system grob))
                 (layout (ly:grob-layout grob))
                 (blot (ly:output-def-lookup layout 'blot-diameter))
                 (staff-space (ly:staff-symbol-staff-space grob))
                 (line-thickness (ly:staff-symbol-line-thickness grob))
                 (half-line-thick (/ line-thickness 2))
                 (grob-thickness (ly:grob-property grob 'thickness 1))
                 (gliss-thick (* line-thickness grob-thickness))
                 ;; We use original not grob here to avoid not catching some
                 ;; elements for broken glissando.
                 (original (ly:grob-original grob))
                 (left-bound (ly:spanner-bound original LEFT))
                 (right-bound (ly:spanner-bound original RIGHT))
                 (left-bound-when (grob::when left-bound))
                 (right-bound-when (grob::when right-bound))
                 ;; Glissando-stencil extents
                 (gliss-stil-x-ext (ly:stencil-extent gliss-stil X))
                 (gliss-stil-y-ext (ly:stencil-extent gliss-stil Y))
                 ;; Glissando-slope
                 ;; The actual used glissando-gradient (calculated below) is 
                 ;; influenced by positive or negative slope.
                 ;; Simple slope could be retrieved by comparing Y-left/right.
                 ;; We need X-left lateron, so we see no real need for 
                 ;; simplification here.
                 (left-bound-info (ly:grob-property grob 'left-bound-info))
                 (X-left (assoc-get 'X left-bound-info))
                 (Y-left (assoc-get 'Y left-bound-info))
                 (right-bound-info (ly:grob-property grob 'right-bound-info))
                 (X-right (assoc-get 'X right-bound-info))
                 (Y-right (assoc-get 'Y right-bound-info))
                 (gliss-slope (/ (- Y-right Y-left) (- X-right X-left)))
                 ;; Glissando-gradient
                 ;; Using the previous found X/Y-left/right would lead to wrong 
                 ;; results for a broken spanner, thus we use the stencil's 
                 ;; extent, choosing the relevant car/cdr relying on the 
                 ;; previous calculated slope.
                 (gliss-left-x (car gliss-stil-x-ext))
                 (gliss-right-x (cdr gliss-stil-x-ext))
                   (if (positive? gliss-slope) 
                       (car gliss-stil-y-ext) 
                       (cdr gliss-stil-y-ext)))
                   (if (positive? gliss-slope) 
                       (cdr gliss-stil-y-ext) 
                       (car gliss-stil-y-ext)))
                     (+ (- gliss-right-y gliss-left-y) 
                        (* (if (positive? gliss-slope) -1 +1) gliss-thick))
                     (- gliss-right-x gliss-left-x)))
                 ;; NoteColumns passed by Glissando
                 ;; If the glissando starts/ends with a note which is beamed 
                 ;; already/further, include the relevant 
                 ;; left/right-bound-NoteColumn.
                 ;; Therefore we look at the Stem of grob's left/right-bound.
                 ;; Include, if present and beamed.
                   (lambda (stem)
                     (and (ly:grob? stem)
                          (ly:grob? (ly:grob-object stem 'beam)))))
                 (sys-elts-array (ly:grob-object sys 'all-elements))
                 ;; Filter for every inner (skipped) NoteColumn 
                     (lambda (elt)
                       (let (;; Going for `ly:grob-relative-coordinate´ disturbs
                             ;; vertical spacing, thus we sort/filter using
                             ;; `grob::when´
                             (elt-when (grob::when elt)))
                           (grob::has-interface elt 'note-column-interface)
                           (ly:grob-property elt 'glissando-skip #f)
                           (ly:moment<? left-bound-when elt-when)
                           (not (moment<=? right-bound-when elt-when)))))
                     (ly:grob-array->list sys-elts-array)))
                 ;; left/right-bound stem
                   (ly:grob-object (ly:spanner-bound grob LEFT) 'stem))
                   (ly:grob-object (ly:spanner-bound grob RIGHT) 'stem))
                 ;; Probably take left/right-bound-NoteColumn into account
                     (if (include-me? left-bound-stem)
                         (list (ly:grob-parent left-bound-stem X))
                     (if (include-me? right-bound-stem)
                         (list (ly:grob-parent right-bound-stem X))
                 ;; All Stems of the relevant NoteColumns 
                     (lambda (nc) (ly:grob-object nc 'stem))
                   (lambda (nc)
                       (ly:grob-object nc 'stem) sys X)))
                   (map stem-x-coord-proc ncs))
                 (left-padding (assoc-get 'padding left-bound-info))
                 ;; Intersection-points of Glissando and Stems
                     (lambda (stem-x-coord)
                         ;; For further coding of the "stemmed-glissando" the 
                         ;; car (representing the X-coordinate) of the here 
                         ;; calculated pair is not needed. We let it in here to
                         ;; gain the possibility to add the cross-stencils for
                         ;; debugging (see below).
                            (- stem-x-coord X-left)
                            (- gliss-left-x left-padding)
                            ;; TODO Is half-line-thick really what we want here?
                            ;;      Postponed, because we don't really need this
                            ;; Calculate and add the Y-value of the glissando at
                            ;; the given stem: we multiply the distance
                            ;; from gliss-start to stem (modified by 
                            ;; left-padding) with the gradient. 
                            ;; NB The result is relative to zero-staff-line.
                               (- stem-x-coord X-left left-padding))
                            ;; Add the relevant value of Glissando's Y-extent:
                            ;; If Glissando points down, use top Y-extent,
                            ;; if it points up use bottom Y-extent.
                            ;; Both corrected by half gliss-thick.
                            (if (negative? gliss-gradient)
                                (- (cdr gliss-stil-y-ext) (/ gliss-thick 2))
                                (+ (car gliss-stil-y-ext) (/ gliss-thick 2))))))
            ;; For conveniance/debugging
            ;; Add cross-stencils where Stem and Glissando intersect
            ;  (make-cross-stencil coords #:optional (thick 0.1) (sz 0.2))
            ;  (ly:stencil-add
            ;    (make-line-stencil 
            ;      thick
            ;      (- (car coords) sz) 
            ;      (- (cdr coords) sz)
            ;      (+ (car coords) sz) 
            ;      (+ (cdr coords) sz))
            ;    (make-line-stencil 
            ;      thick
            ;      (- (car coords) sz) 
            ;      (+ (cdr coords) sz)
            ;      (+ (car coords) sz) 
            ;      (- (cdr coords) sz))))
            ;  (ly:grob-set-property! grob 'stencil
            ;    (apply ly:stencil-add
            ;      (ly:grob-property grob 'stencil)
            ;        (map make-cross-stencil gliss-stem-intersections)))
            ;; Color passed note-heads
            ;  (lambda (nh)
            ;    (ly:grob-set-property! nh 'transparent #f)
            ;    (ly:grob-set-property! nh 'stencil (ly:note-head::print nh))
            ;    (ly:grob-set-property! nh 'color cyan))
            ;  (append-map
            ;    (lambda (nc)
            ;      (ly:grob-array->list (ly:grob-object nc 'note-heads)))
            ;    ncs))
            ;; Color left/right bound
            ;(ly:grob-set-property! left-bound 'color red)
            ;(ly:grob-set-property! right-bound 'color green)
            ;;  Recreate Stem.stencil to match the glissando 
            ;;  Move Flag
            ;;  Move Script
            ;;  Recreate Beam.stencil, probably relying on user-specifications
              (lambda (gsi stem)
                (let* (
                  (stem-y-ext (ly:grob-extent stem stem Y))
                  (stem-dir (ly:grob-property stem 'direction))
                  (pap-col (ly:item-get-column stem))
                  (pap-col-elts-array (ly:grob-object pap-col 'elements))
                      (lambda (elt)
                        (grob::has-interface elt 'script-interface))
                      (ly:grob-array->list pap-col-elts-array)))
                  (beam (ly:grob-object stem 'beam))
                    (if (ly:grob? beam)
                        (ly:grob-array->list (ly:grob-object beam 'stems))
                  ;; Get the the shortest duration of a Beam with mixed
                  ;; durations. We go for the maximum of duration-log 
                    (if beamed-stems
                            (lambda (stem)
                              (ly:grob-property stem 'duration-log))
                  ;; Calculate beam-gradient, if beam is present
                  ;; Get a possible user-override for
                  ;; Beam.details.beamed-glissando-stem-positions
                    (if (ly:grob? beam)
                        (ly:grob-property beam 'details)
                    (if beam-details
                    (if (ly:grob? beam)
                        (ly:grob-property beam 'X-positions)
                  ;; Calculate beam-gradient, but only if the user 
                  ;; specified an override for 
                  ;; Beam.details.beamed-glissando-stem-positions
                    (if beamed-glissando-stem-positions
                           (- (cdr beamed-glissando-stem-positions)
                              (car beamed-glissando-stem-positions))
                           (- (cdr beam-x-positions)
                              (car beam-x-positions)))
                  ;; If details.beamed-glissando-stem-positions is set,
                  ;; the usual calculation (further below) of the stem's 
                  ;; length will fail.
                  ;; Thus we need to calculate some values to have the 
                  ;; beamed stems fit into said beam. These values are 
                  ;; stored together with their Stem-grob in an alist and 
                  ;; referenced below.
                    (if beamed-glissando-stem-positions
                        (let* ((beamed-ncs
                                   (lambda (stem) (ly:grob-parent stem X))
                                 (map stem-x-coord-proc beamed-ncs))
                                   (lambda (coord) 
                                     (- coord (car x-coords)))
                                   (lambda (stem coord)
                                     (cons stem (* coord beam-gradient)))
                    (ly:grob-property grob 'details))
                      (+ (cdr gsi) 
                         (* stem-dir 
                            ;; If the glissando starts/ends with a note 
                            ;; which is beamed already/further, don't 
                            ;; apply pad-y to this stem.
                            (if (or (equal? stem left-bound-stem)
                                    (equal? stem right-bound-stem))
                      (if beamed-glissando-stem-positions
                          ;; If a Beam is present *and* 
                          ;; beamed-glissando-stem-positions is set, use 
                          ;; the car of it for the initial Stem and add the 
                          ;; values of beamed-stem-corrs as appropriate.
                          (+ (car beamed-glissando-stem-positions) 
                             (assoc-get stem beamed-stem-corrs))
                          ;; Otherwise calculate the Stems independently,
                          ;; aiming at Beams parallel to the glissando
                          ;; and else equal stem-lengths for equal 
                          ;; durations.
                          ;; Rely on `beamed-lengths´ and `lengths´ from
                          ;; Stem.details for it, so a user-override is
                          ;; respected.
                          (let* ((stem-details 
                                   (ly:grob-property stem 'details)))
                                  (if (and beamed-stems-max-dur 
                                           (member stem beamed-stems))
                                      ;; For beamed Stems we always add the
                                      ;; same value to warrant parallel Beam
                                      ;; and Glissando. For Beams with
                                      ;; mixed durations we look at their
                                      ;; shortest duration, i.e. the maximum
                                      ;; of the stems duration-logs and take
                                      ;; this value for further calculation
                                             ;; default:
                                             '(3.26 3.5 3.6)))
                                           (if (> intlog 4)
                                               (last beamed-lengths)
                                                 beamed-lengths (- intlog 3)))))
                                       ;; TODO little clumsy here...
                                       ;; probably use other Stem.details like:
                                       ;; - beamed-minimum-free-lengths
                                       ;; - beamed-extreme-minimum-free-lengths
                                       ;; - stem-shorten
                                       ;; as well.
                                       (+ stem-length
                                          ;; My choice - Harm
                                          (* 0.5 (- intlog 3))))
                                      ;; For unbeamed Stems of 8th notes and
                                      ;; shorter we add some value relying on
                                      ;; their duration-log
                                             ;; default
                                             '(3.5 3.5 3.5 4.25 5.0 6.0)))
                                             stem 'duration-log))
                                             ((= intlog 1)
                                               (car unbeamed-lengths))
                                             ((<= intlog 
                                                     (length unbeamed-lengths)))
                                                 (- intlog 2)))
                                             (else (last unbeamed-lengths)))))
                               (cdr gsi) 
                               (* stem-dir pad-y))))))
                  (flag (ly:grob-object stem 'flag))
                    (if (ly:grob? flag)
                        (ly:grob-property flag 'stencil #f)
                  ;; recreate Beam.stencil 
                  ;; Relies on new setting of 'positions derived from 
                  ;;  new-stem-y-ext.
                  ;; Renewing quantized-positions is needed to get the stencil 
                  ;; correct.
                  ;; The new beam is always parallel to the glissando, unless a
                  ;; user-override takes priority.
                  (if (ly:grob? beam)
                        (ly:grob-set-property! beam 'positions 
                          (if beamed-glissando-stem-positions
                              ;; TODO below results in resetting Beam.positions
                              ;; with every stem of the Beam. 
                              ;; How to simplify?
                                (if (equal? stem (car beamed-stems))
                                    (if (positive? stem-dir)
                                        (cdr new-stem-y-ext)
                                        (car new-stem-y-ext))
                                    (car (ly:grob-property beam 'positions)))
                                (if (equal? stem (last beamed-stems))
                                    (if (positive? stem-dir)
                                        (cdr new-stem-y-ext)
                                        (car new-stem-y-ext))
                                    (cdr (ly:grob-property beam 'positions))))))
                        (ly:grob-set-property! beam 'quantized-positions 
                          (ly:beam::set-stem-lengths beam))
                          beam 'stencil (ly:beam::print beam))))
                  ;; move scripts according to new Stem.stencil below
                  ;; Remark: Script will be positioned always at NoteHead-side, 
                  ;; never at Stem-side
                  (if (pair? scripts)
                        (lambda (i script)
                          (let* ((script-stil 
                                   (ly:grob-property script 'stencil))
                                   (ly:grob-property script 'Y-offset))
                                   (ly:grob-property script 'padding)))
                            ;; TODO Scripts should avoid staff-lines!
                            ;;      Special-case some scripts?
                            (ly:grob-set-property! script 'stencil
                                (ly:grob-property script 'stencil)
                                   ;; move script to zero-line
                                   (- script-y-off) 
                                   ;; move script to glissando-line
                                   (cdr gsi)
                                   ;; Apply one staff-space padding for each 
                                   ;; script.
                                   ;; NB There are probably multiple ones per 
                                   ;; NoteColumn
                                   (* i staff-space stem-dir -1)
                                   (* script-padding stem-dir -1))
                        (iota (length scripts) 1 1)
                  ;; move Flag.stencil according to new Stem.stencil below
                  (if flag-stil
                      (ly:grob-set-property! flag 'stencil 
                          (if (positive? stem-dir)
                              (- (cdr new-stem-y-ext) (cdr stem-y-ext))
                              (- (car new-stem-y-ext) (car stem-y-ext)))
                  ;; recreate a new Stem.stencil
                  (ly:grob-set-property! stem 'stencil
                      (ly:grob-extent stem stem X) 
%% Mmmh, the namings are misleading. The \arpeggio together with the applied
%% `glissando-and-stems´ do the job. The two following definitions are merely
%% helpers.
startStemmedGlissando = {
  \temporary \override NoteColumn.glissando-skip = ##t
  \temporary \override NoteHead.stem-attachment = #'(0 . 0)
  %% making NoteHeads transparent, rather than outputting point-stencil
  %% makes for better spacing
  %\temporary \override NoteHead.stencil = #point-stencil
  \temporary \override NoteHead.transparent = ##t
  \temporary \override = ##t
  \temporary \override Accidental.stencil = ##f
  %% Do we need the line below?
  %\temporary \override = ##t

stopStemmedGlissando = {
  \revert NoteColumn.glissando-skip
  \revert NoteHead.stem-attachment
  %\revert NoteHead.stencil
  \revert NoteHead.transparent
  \revert Accidental.stencil


"\tSome TODOS for stemmed Glissando:
\t- What about Dots in skipped NoteColumns, make them invisible?
\t- Should we add other defaults for Stem.lengths/beamed-lengths?
\t- Some minor TODOs inline...\n")

%% Not essential, only to ease testings
multipleTransposes =
#(define-music-function (parser location m music)(ly:music? ly:music?)
   (music-clone m
    (map (lambda (pitch)
	  (ly:music-property #{ \transpose c $pitch $music #} 'element))
         (event-chord-pitches m))))

\paper { ragged-right = ##f }

% {

tst-I = 
\transpose b b'
\new Staff {
  \override Glissando.breakable = ##t
  \override Glissando.after-line-breaking = #(glissando-and-stems 0)
  b2 b4 b
  \override Stem.details.beamed-lengths = #'(2.26 2.5 2.6)
  \repeat unfold 4 { c'16 d'32*2 d'64*4 e'128*8-> f' } 
  \revert Stem.details.beamed-lengths
  \repeat unfold 4 { c'16 d' e' f' }
  \repeat unfold 4 { c'16 d' e' f' } 

\score { \tst-I \layout { ragged-right = ##t \autoBeamOff } }

\score { \tst-I \layout { ragged-right = ##f \autoBeamOn } }
% {
  \override Glissando.breakable = ##t
  \override Glissando.after-line-breaking = #(glissando-and-stems 0)
  \once \override Beam.details.beamed-glissando-stem-positions = #'(4 . 8)
  \repeat unfold 4 { c'16 d' e' f' } 
  \repeat unfold 4 { c'16 d' e' f' } 
  \repeat unfold 2 { c'16 d' e' f' } 
  %% The Glissando ends in the middle of some beamed notes.
  %% `glissando-and-stems´ is not smart enough to deal with this situation, thus
  %% some manual intervenrion is needed.
  \once \override Beam.details.beamed-glissando-stem-positions = #'(3 . 3.2)
  a' a' 
  a' a' 

% {
\transpose c c'
  \override Glissando.after-line-breaking = #(glissando-and-stems 0)
%  \override Glissando.breakable = ##t
  %% For automatic Beams, set the values carefully
  \once \override Beam.details.beamed-glissando-stem-positions = #'(-4 . -8)
  \repeat unfold 4 bes'32-.
  %% For manual Beams set the direction accordingly.
  \once \override Beam.details.beamed-glissando-stem-positions = #'(4 . 8)
%  \break
  \stemUp g'2

mus = {
  \override Stem.details.beamed-lengths = 
       3.26  ;; 8
       3.5   ;; 16
       3.6  ;; 32 etc

% {	
\multipleTransposes { 
	c, d, e,  f, g, 
	a, b, 
	c d e f 
	g a b 
  \override NoteHead.layer = -1000
  \override Glissando.breakable = ##t
  \override Glissando.after-line-breaking = #(glissando-and-stems 0)

Reply via email to