Am Di., 5. Okt. 2021 um 13:39 Uhr schrieb Leo Correia de Verdier
<[email protected]>:
>
> Dear Harm and list!
>
> After almost two years I’m back using this code and found some things I would 
> need help with.
> (I have done some additions to it, so it can hackishly handle additional 
> noteheads on the stems/notecolumns and also tweaks rests passed by the 
> glissando line, byt as I’m not very experienced with code they’re too ugly to 
> be posted publicly)
>
> First, when adding more staves the function for the scripts catches the 
> scripts of simultaneous note columns in other staves too. I have tried to 
> solve this by comparing the staff-symbol associated with them and tried 
> getting to them via the NoteColumns, but with no success.
>
> Second, when beam slopes get steep the stems in the middle don’t attach to 
> the beams.
>
> Third on the outmost notes, with visible noteheads, the stem connections of 
> the noteheads go off. I think the solution would be to use original values 
> for their stems instead of calculating them, but that would require some 
> extra code for the beams…
>
> If two and three aren’t easy (just that I haven’t been able to solve them) 
> they could be bypassed by allowing manual settings for stems, something like 
> it’s done with beams.
>
> Would you have time to help me with this?
>
> Thanks!
> /Leo
>
> > 26 okt. 2019 kl. 12:39 skrev Thomas Morley <[email protected]>:
> >
> > Am Fr., 25. Okt. 2019 um 22:41 Uhr schrieb Leo Correia de Verdier
> > <[email protected]>:
> >
> >> 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 :((
> >
> >
> > Best,
> >  Harm
> >
>

Hi Leo,

sorry for the late reply.
During this year's autumn break I had to finish some huge own work,
before I had the opportunity to look into stemmed Glissando again.

While I can confirm the issues you mentioned, I think the approach via
Glissando.after-line-breaking is not the promising to be finally
successful.
Too many hassles and flaws...

Thus I tried to follow a different route, not tackling Glissando at
all, but Stems, Beams and NoteHeads, including an engraver (mostly to
set certain pointers).
Afaict, the above mentioned issues are gone, alas some others arose.
The most annoying with added Script.

Meanwhile I got some hints why it happens and how to solve (probably).
Alas. my autumn break ends soon, I doubt I can proceed much.
Thus, I attach the current state.

Please, be aware, it's only an intermediate state!

Cheers,
  Harm
\version "2.22.1"

#(define glissando-stems
  (lambda (grob)
"If @code{NoteColumn.glissando-skip} is set to this procedure, a glissando
skips this @code{NoteColumn} grob and the @code{details.glissando-stems}
property is set @code{#t}."
   (ly:grob-set-nested-property! grob '(details glissando-stems) #t)
   #t))

#(define-public (line-spanner-gradient stencil grob)
"Takes a line-spanner @var{grob} grob and returns the gradient of the line
looking at @var{stencil}."
  (if (and (grob::has-interface grob 'line-interface)
           (grob::has-interface grob 'line-spanner-interface))
      (let* ((left-bound-info (ly:grob-property grob 'left-bound-info))
             (Y-left (assoc-get 'Y left-bound-info))
             (right-bound-info (ly:grob-property grob 'right-bound-info))
             (Y-right (assoc-get 'Y right-bound-info))
             (x-ext (ly:stencil-extent stencil X))
             (y-ext (ly:stencil-extent stencil Y))
             (line-thickness (ly:staff-symbol-line-thickness grob))
             (grob-thickness (ly:grob-property grob 'thickness #f))
             (thick (or grob-thickness line-thickness)))
         (/
           (- (interval-length y-ext) thick)
           (- (interval-length x-ext) thick)
           (if (>= (- Y-right Y-left) 0) 1 -1)))
      (begin
        (ly:warning "Grob ~a is not a line-spanner, returning zero" grob)
        0)))

#(define-public (line-spanner-stem-intersection-points refp note-columns grob)
"Takes the @code{Stem} grobs of @var{note-columns} and calculates the
intersection points of @var{grob}, supposed to be a line-spanner, with Y-axis of
the @code{Stem} grobs."
  (let* ((grob-relative-coord (ly:grob-relative-coordinate grob refp X))
         ;; TODO use thicknesses at all? See next TODO
         ;(line-thickness (ly:staff-symbol-line-thickness grob))
         ;(half-line-thick (/ line-thickness 2))
         ;(grob-thickness (ly:grob-property grob 'thickness #f))
         ;(thick (or grob-thickness line-thickness))
         ;; stencil-stuff
         ;; NB ly:line-spanner::print adds half-line-thick to both sides of
         ;; the line
         (stil (ly:line-spanner::print grob))
         (stil-x-ext (ly:stencil-extent stil X))
         (stil-y-ext (ly:stencil-extent stil Y))
         ;; left-bound-stuff
         (left-bound-info (ly:grob-property grob 'left-bound-info))
         (X-left (assoc-get 'X left-bound-info))
         (gradient (line-spanner-gradient stil grob))
         ;; stem-stuff
         (stems
           (filter-map
             (lambda (nc) (ly:grob-object nc 'stem #f))
             note-columns))
         (stems-x-coords
           (map
             (lambda (stem) (ly:grob-relative-coordinate stem refp X))
             stems)))
    ;; resulting intersection-points
    (map
      (lambda (stem-x-coord)
        (cons
          ;; X-coord
          (- stem-x-coord
             grob-relative-coord)
          ;; Y-coord
          (+
             ;; Calculate and add the Y-value of the line-spanner at
             ;; the given stem
             ;; NB The result is relative to zero-staff-line.
             (*
                gradient
                (- stem-x-coord
                   grob-relative-coord
                   (car stil-x-ext)))
             ;; Thus add the relevant value of line-spanner's y-extent:
             ;; If the line-spanner points down, use top y-extent,
             ;; if it points up use bottom y-extent.

             ;; TODO corrected by half thick? I.e.:
             ;(if (negative? gradient)
             ;    (- (cdr stil-y-ext) (/ thick 2))
             ;    (+ (car stil-y-ext) (/ thick 2)))
             (if (negative? gradient)
                 (cdr stil-y-ext)
                 (car stil-y-ext)))))
      stems-x-coords)))

#(define adjust-stems-beams
  (lambda (grob)
"Moves @code{NoteHead} grobs passed by one or more glissando lines to the most
distant glissando line, viewed from the @code{Stem} side.
The @code{Stem.details} subproperties for @code{lengths} (for unbeamed
@code{Stem} grobs) and @code{beamed-minimum-free-lengths} (for beamed
@code{Stem} grobs) are adjusted to avoid most collisions with glissando lines."
    (let* ((raw-stems
             (cond ((grob::has-interface grob 'stem-interface) (list grob))
                   ((grob::has-interface grob 'beam-interface)
                    (let ((stems-array (ly:grob-object grob 'stems)))
                      (if (ly:grob-array? stems-array)
                          (ly:grob-array->list stems-array)
                          '())))
                   (else '())))
           (stems
             (filter
               (lambda (stem)
                 (ly:grob-object stem 'glissandi #f))
               raw-stems)))
      ;; Proceed only with stems with a passing Glissando, stop if there
      ;; arent' any.
      (if (pair? stems)
          (let* (;; We search all Stems, because sometimes a Glissando starting
                 ;; at a beamed Stem may be overlooked.
                 ;; Delete duplicate findings.
                 (glissandi
                   (delete-duplicates
                     (append-map
                       (lambda (stem)
                         (let ((glissandi-array
                                 (ly:grob-object stem 'glissandi #f)))
                            (if glissandi-array
                                (ly:grob-array->list glissandi-array)
                                '())))
                       stems)))
                 ;; Get the bounds of a Glissando. We may not wish to move these
                 ;; NoteHeads, see below
                 (gliss-bounds
                   (append-map
                     (lambda (gliss)
                       (list
                         (ly:spanner-bound gliss LEFT)
                         (ly:spanner-bound gliss RIGHT)))
                     glissandi))
                 ;; All NoteColumns: start, end and in-between
                 (parent-ncs
                   (map
                     (lambda (stem) (ly:grob-parent stem X))
                     stems))
                 (nhds-lists
                   (map
                     (lambda (nc)
                       (let* ((nhds-array (ly:grob-object nc 'note-heads #f)))
                           (if nhds-array
                               (ly:grob-array->list nhds-array)
                               '())))
                     parent-ncs))
                 ;; All NoteHeads with their 'staff-position
                 ;; NB It is a nested list with sublists of note-heads per
                 ;; note-column
                 (nhds-staff-pos-lists
                   (map
                     (lambda (nhds)
                       (map
                         (lambda (nhd)
                           (cons nhd (ly:grob-property nhd 'staff-position)))
                         nhds))
                     nhds-lists))
                 (stem-gliss-intersection-points
                   (map
                     (lambda (gliss)
                       (line-spanner-stem-intersection-points
                         (ly:grob-system grob)
                         parent-ncs
                         gliss))
                     glissandi))
                 ;; We are interested in the y-coords of each intersection point
                 ;; y-coords per Glissando
                 (stem-gliss-y-list
                   (map
                     (lambda (l) (map cdr l))
                     stem-gliss-intersection-points))
                 ;; y-coords per Stem
                 (y-list
                   (if (pair? stem-gliss-y-list)
                       (apply zip stem-gliss-y-list)
                       '()))
                 (max-ys
                   (if (pair? y-list)
                       (map (lambda (x) (apply max x)) y-list)
                       '()))
                 (min-ys
                   (if (pair? y-list)
                       (map (lambda (x) (apply min x)) y-list)
                       '()))
                 (max-diffs
                   (map
                     (lambda (y1 y2) (- y1 y2))
                     max-ys
                     min-ys))
                 (dir (ly:grob-property grob 'direction))
                 (relevant-y-vals
                   (if (positive? dir)
                       min-ys
                       max-ys)))
                       #t
            ;; move note-heads to glissando line
            (for-each
              (lambda (nhds-staff-pos-list val)
                (for-each
                  (lambda (nhd-staff-pos)
                    (if (and (pair? nhd-staff-pos)
                             (not (member (car nhd-staff-pos) gliss-bounds)))
                        (begin
                          (ly:grob-translate-axis!
                            (car nhd-staff-pos)
                            ;; If NoteHead.Y-offset is set zero before, its
                            ;; sufficient to use `val', otherwise take
                            ;; half of staff-position into account
                            val
                            ;(+ (/ (cdr nhd-staff-pos) -2) val)
                            Y))))
                  nhds-staff-pos-list))
              nhds-staff-pos-lists
              relevant-y-vals)

            ;; adjust Stem.details.lengths by the current distance between
            ;; top and bottom glissandi lines, will be zero if only one
            ;; glissando is present
            (if (grob::has-interface grob 'stem-interface)
                (ly:grob-set-nested-property! grob
                  '(details lengths)
                  (map
                    (lambda (x) (+ (car max-diffs) x))
                    (assoc-get 'lengths (ly:grob-property grob 'details)))))

            ;; adjust beamed-minimum-free-lengths
            (if (grob::has-interface grob 'beam-interface)
                (for-each
                  (lambda (stem val)
                    (let ((details (ly:grob-property stem 'details)))
                      (ly:grob-set-nested-property! stem
                        '(details beamed-minimum-free-lengths)
                           (map
                             (lambda (x) (+ x val))
                             (assoc-get
                               'beamed-minimum-free-lengths details)))))
                  stems
                  max-diffs)))))))

#(define stem::adjust-for-glissando
  (lambda (grob)
"Moves @code{NoteHead} grobs of an unbeamed @code{Stem} grob to a glissando
line.  This procedure is supposed to be the value of the @code{Stem.stencil}
property."
    ;; If Beam is present simply return default stencil.
    ;; Else move NoteHead grobs to sit upon the most distant glissando line
    ;; (viewed from Stem).
    ;; If only one glissando line is present, take that line.
    ;; Stem.details.lengths for multiple glissando lines is adjusted.
    ;; Finally return (new) default stencil.
    (let* ((beam (ly:grob-object grob 'beam)))
      (if (ly:grob? beam)
          (ly:stem::print grob)
          (begin
            ;; Move NoteHeads to glissando line
            (adjust-stems-beams grob)
            ;; return default stencil
            (ly:stem::print grob))))))

#(define beam::adjust-for-glissando
  (lambda (grob)
"Moves @code{NoteHead} grobs of a beamed @code{Stem} grob to a glissando line.
Respects user overrides for @code{Beam.details.glissando-positions}, let them
replace the default @code{(beam::place-broken-parts-individually grob)}.
This procedure is supposed to be the value of the @code{Beam.positions}
property."
;; Overriding Beam.positions with this procedure needs to be applied
;; before the Beam is started.
;; Thus we put it as general setting in the engraver, see below.
    (let* ((positions (assoc-get 'positions (ly:grob-basic-properties grob))))

      ;; Move NoteHeads to glissando line
      ;; Stem.details.beamed-minimum-free-lengths is adjusted for multiple
      ;; glissando lines
      (adjust-stems-beams grob)

      ;; Respect user-override for Beam.positions
      (if (procedure? positions)
          (positions grob)
          positions))))

#(define Glissando_pointers_engraver
  (lambda (context)
"Adds pointers from @code{Glissando} grobs to @code{NoteColumn} grobs and
from @code{Stem} grobs to passing @code{Glissando} grobs.
Sets @code{Beam.positions} to @code{beam::adjust-for-glissando}."
    (let ((start-glissando #f)
          (note-columns '())
          (glissandi '())
          (passed-stems '()))
      (make-engraver
        (acknowledgers
          ((beam-interface engraver grob source-engraver)
            (ly:grob-set-property! grob 'positions beam::adjust-for-glissando))
          ((glissando-interface engraver grob source-engraver)
            (set! start-glissando #t)
            (set! glissandi (cons grob glissandi)))
          ((note-column-interface engraver grob source-engraver)
            (let* ((details (ly:grob-property grob 'details))
                   (glissando-stems? (assoc-get 'glissando-stems details #f)))
              (if (and start-glissando glissando-stems?)
                  (let* ((nhds (ly:grob-object grob 'note-heads))
                         (nhds-list
                           (if (ly:grob-array? nhds)
                               (ly:grob-array->list nhds)
                               '()))
                         (stem (ly:grob-object grob 'stem)))
                    (set! passed-stems (cons stem passed-stems))
                    (set! note-columns (cons grob note-columns)))))))
        (end-acknowledgers
          ((glissando-interface engraver grob source-engraver)
             (let* ((left-bound (ly:spanner-bound grob LEFT))
                    (right-bound (ly:spanner-bound grob RIGHT))
                    (left-nc
                      (if (grob::has-interface
                            left-bound
                            'note-head-interface)
                          (list (ly:grob-parent left-bound X))
                          '()))
                    (right-nc
                      ;; check for right-bound being a grob, for glissandi
                      ;; between unequal amounts of notes it may be not
                      (if (and (ly:grob? right-bound)
                               (grob::has-interface
                                 right-bound
                                 'note-head-interface))
                          (list (ly:grob-parent right-bound X))
                          '())))

               ;; put NoteColumn grobs into a 'note-columns-pointer of
               ;; Glissando, technically not needed for stemmed glissandi, but
               ;; nice to have
               (for-each
                 (lambda (nc)
                   (ly:pointer-group-interface::add-grob
                     grob 'note-columns nc))
                 (append left-nc (reverse note-columns) right-nc))

               ;; add a pointer named 'glissandi to every passed Stem,
               ;; containing the Glissando grobs passing this Stem
               ;; exclude those glissandi without proper bounds, see above
               (for-each
                 (lambda (gliss)
                   (for-each
                     (lambda (stem)
                       (if (and (ly:grob? left-bound) (ly:grob? right-bound))
                           (ly:pointer-group-interface::add-grob
                             stem 'glissandi gliss)))
                     passed-stems))
                 glissandi)

               (set! start-glissando #f)
               (set! passed-stems '())
               (set! glissandi
                     (remove (lambda (gliss) (equal? grob gliss)) glissandi))
               (if (null? glissandi)
                   (set! note-columns '())))))))))

#(ly:register-translator
 Glissando_pointers_engraver 'Glissando_pointers_engraver
 '((grobs-created . ())
   (events-accepted . ())
   (properties-read . ())
   (properties-written . ())
   (description . "\
Engraver to set pointers from @code{Glissando} grobs to @code{NoteColumns} grobs
and from @code{Stem} grobs to @code{Glissando} grobs.
Setting @code{Beam.positions} to @code{beam::adjust-for-glissando}")))

glissandoStemsOn =
#(define-music-function (mus) (ly:music?)
"@var{mus} is supposed to be a @code{note-event} or @code{event-chord} with
a starting glissando.
Applies certain overrides to get stems at the glissando."
;; We do it as a music-function to gain he possibility to set it before `mus'.
;; This way it is more in line with the usage of commands like
;; \arpeggioArrowUp etc
  #{
     $mus
     \temporary \override NoteColumn.glissando-skip = #glissando-stems
     \temporary \override NoteHead.stem-attachment = #'(0 . 0)
     %% TODO keep this one?
     %\temporary \override Stem.no-stem-extend = ##t
     \temporary \override Stem.stencil = #stem::adjust-for-glissando

     \temporary \override NoteHead.no-ledgers = ##t
     \temporary \override Accidental.stencil = ##f

     %% Don't confuse LilyPond with suspended NoteHeads
     \temporary \override NoteHead.X-offset = ##f

     %% Debugging helpers
     %\temporary \override NoteHead.color = #grey
     %\temporary \override NoteHead.layer = -1000

     %% Make NoteHead transparent, omitting stencil or using point-stencil
     %% will sometimes result in bad horizontal spacing
     \temporary \override NoteHead.transparent = ##t
     \temporary \override NoteHead.Y-offset = 0
  #})

glissandoStemsOff = {
  \revert NoteColumn.glissando-skip
  \revert NoteHead.stem-attachment
  \revert NoteHead.X-offset
  % \revert Stem.no-stem-extend
  \revert Stem.stencil
  \revert Stem.positioning-done
  \revert NoteHead.no-ledgers
  \revert Accidental.stencil
  \revert NoteHead.color
  \revert NoteHead.layer
  \revert NoteHead.transparent
  \revert NoteHead.Y-offset
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% USAGE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#(ly:set-option 'debug-skylines #t)

\paper {
  indent = 0
  ragged-right = ##t
}

\layout {
  \context {
    \Voice
    \override Glissando.breakable = ##t
    \consists "Glissando_pointers_engraver"
  }
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ISSUES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\markup
  \rounded-box \rounded-box
    \fill-line { \fontsize #3 \bold "Issues" \null }

%% Script
\markup
  \pad-around #2
  "01 Script-grobs etc may be placed wrongly (with workaround)"

correctScripts =
#(define-music-function (val)(number?)
#{
  \once \override Script.quantize-position = ##f
  \once \override Script.staff-padding  = $val
  \once \override Fingering.staff-padding = $val
  \once \override StringNumber.staff-padding = $val
  \once \override StrokeFinger.staff-padding = $val
#})

<<
  {
    \glissandoStemsOn
    b''4\glissando
    g''-.-!->
    \glissandoStemsOff
    b'''2
  }
  {
    \glissandoStemsOn
    b''4\glissando
    \correctScripts 3.6
    g''-.-!->
    \glissandoStemsOff b'''2
  }
>>

%% Beam.direction
\markup
  \pad-around #2
    "02 Direction of Beams differ with stemmed glissando. Work around with
\\voiceXxx, \\stemUp ..."

<<
  \relative c'' {
    c16b a g
    f e d c
    b a g f
    e d c b
  }

  \relative c'' {
    \glissandoStemsOn
    c16\glissando b a g
    f e d c
    b a g f
    e d c \glissandoStemsOff b
  }
>>

%%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%%
\markup
  \rounded-box \rounded-box
    \fill-line { \fontsize #3 \bold "Examples" \null }

% {
\markup \pad-around #2 "Multiple Glissandi, chords, Beam over Rest, line breaks"

\transpose c c' {
  %\voiceTwo
  \glissandoStemsOn
  <ces e'>4\glissando
  <ees c'''' >128*32[
  f''''16*2-!-.->
  r
  <cis'' e''>8*2]-!-.->
  \glissandoStemsOff
  \glissandoStemsOn
  <e'' gis''>2-!
  \glissando
  <e'' g''>4
  \glissandoStemsOff
  q

  \break

  \glissandoStemsOn
  <ces' e'>1\glissando
  <c'' e''>4 <c'' e''''>8[-!-.-> <c'' e''''>]-!-.-> q4 q
  \break
  <c'' e''>8
  r
  <cis'' e''>4
  \glissandoStemsOff
  \set glissandoMap = #'((0 . 0) (0 . 1))
  \glissandoStemsOn
  e''2\glissando
  gis''8*8
  \glissandoStemsOff
  <e' gis''>1
}

%}
% {
\markup
  \pad-around #2
  "Restricted to current Staff, user override for Beam.positions is respected"

<<
  \new Staff \transpose c c {
    \voiceOne
    \glissandoStemsOn
    b8\glissando
    c'16 d
    \once \override Beam.positions = #'(5 . 10)
    e' f' g' c'16 d' e' f' g'
    c'16 d'
    \glissandoStemsOff
    b'8
  }
  \new Staff \fixed c'' {
    d'8-> g-> c-> f,-> bes,,-> ees,,->r4
  }
>>
%}
% {
\markup \pad-around #2 "Start/end inside Beam"

{
  g16[
    ais
    \glissandoStemsOn
    b\glissando
    b
    d'' e bis bes \glissandoStemsOff a'

    b']
}
%}
% {

\markup \pad-around #2 "Steep Glissandi"
{
  \set beamExceptions = #'()
  \voiceTwo
  \glissandoStemsOn d'''8\glissando[ g'' c'' f'] \glissandoStemsOff bes2
  \glissandoStemsOn d'''8 \glissando g'' c'' f' \glissandoStemsOff bes2
}
%}

Attachment: glissando-stems-engraver-08.pdf
Description: Adobe PDF document

Reply via email to