Am Mo., 21. Dez. 2020 um 20:41 Uhr schrieb Klaus Blum <benbigno...@gmx.de>:
>
> Hi Gilberto,
>
> Am 21.12.2020 um 17:51 schrieb lilypond-user-requ...@gnu.org:
>
> > Ideally, I think applying it to a chord (something like \cowellCluster
> > <c' a'>4) would be the ideal solution. It also does not handle whole
> > notes, as those do not have stems to be hacked. If I come up with
> > something interesting I will make sure to post it here.
>
> please have a look at the link to the German forum (the thread there
> continues in English):
> https://lilypondforum.de/index.php/topic,820.msg4546.html?PHPSESSID=6djk9rm3a7m81mmuqu25mjiehb#msg4546
>
> I've posted an improved version there, and Harm has an even more
> interesting solution.
>
> Cheers,
> Klaus
>

Hi Gilberto,

I reworked the code, see
https://lilypondforum.de/index.php/topic,820.msg4562.html#msg4562
You'll need an account there to get the files, thus I attach them here as well.

Cheers,
  Harm
\version "2.20.0"

\include "cowell-clusters.ly"
\include "other-stuff.ly"

upI = {
  \tempo "Allegro feroce"
  r2 r8
  <g! ais cis' fis'>\ff \repeat unfold 10 <g ais cis' fis'>
  <a c' dis' gis'>^-\< <a c' dis' fisis' gis'>
  <a c' dis' eis' gis'> <a c' dis' fis' gis'>
  <a c' dis' gis'> <a c' e' gis'>
  <a cis' gis'> <a c' gis'>
  
  \break
  \pseudoIndent 8.5
  
  <>^\tweak padding #2
    -\markup
    \override #'(box-padding . 0.8)
      \box
      \fontsize #0
      \italic
      "For explanations and playing instructions also see inside back cover"
  \voiceOne
  <c' dis' fis' b'>--\!
  \clef "bass"
  <>-\tweak X-offset #-3 
     \mf
     _\markup \fontsize #-1.2 \whiteout "Mit flacher Hand zu spielen"
  \cluster { \repeat unfold 8 <c c'> } 
  \clef "treble"
  <>-\tweak Y-offset #-4 -\tweak X-offset #-3 \ff 
  \repeat unfold 7 e'' 
  e''4. e''8 c'''2
  \oneVoice
  r8\fermata
  \override Slur.details.edge-attraction-factor = 200
  \once \override Beam.positions = #'(-8 . -6)
  ais''^>(
  \change Staff = "down"
  b'^> d''-\tweak avoid-slur #'inside ^>) 
  \change Staff = "up" 
  cis'''8.^>-\shape #'((0 . 0) (1.5 . 1.5) (-1 . 0) (0 . -3))-( 
  fis''16-\tweak avoid-slur #'inside ^>
  
  \eraseShortInstrumentName
      
  \change Staff = "down"
  g'4)^>
  \clef "bass"
  
  \break
  
  \change Staff = "up" 
  fis'''2->~ 8
  \noBeam
  <bes des' e' a'>-\f <bes gis' a'> <bes g' a'>
  \override TupletBracket.bracket-visibility = ##t
  \tuplet 5/4 {
    <bes fis' a'> <bes gis' a'> <bes g' a'> <bes fis'? a'> <bes gis' a'>
  }
  \tuplet 5/4 {
  	<bes g' a'> <bes fis' a'> <bes gis' a'> <bes g' a'> <bes fis' a'>
  }
  \time 2/4 
  <bes gis' a'>[ <bes g' a'> <bes fis' a'> <bes gis' a'>]
}

downI = {
  \clef "bass"
  r2 r8 <e, a, dis> \repeat unfold 10 <e, a, dis>
  <cis, fis, d!>_- \repeat unfold 7 <cis, fis, d>
  <a, b, f>^- b,!^> gis,^> ais,^> e,^> fis,^> c,^> bes,,^>
  a,,^>\noBeam -\tweak X-offset #2 -\tweak padding #5 _"C"
  <>_\markup 
       \fontsize #-1.2
       \whiteout 
       "Mit beiden Vorderarmen gleichzeitig zu"
    _\markup \fontsize #-1.2 "spielen"
  \cluster ##t {
  	\autoBeamOff
    \repeat unfold 7 c,,
    c,,4. c,,8 
    \once\override Score.NoteColumn.X-offset = 0.5
    a,,2
  }
  \autoBeamOn
  \clef "treble"
  s1

  \eraseShortInstrumentName
      
  \once \override Staff.Clef.before-line-breaking = 
    #(lambda (grob)
      (if (= (ly:item-break-dir grob) -1)
          (ly:grob-set-property! grob 'X-extent '(-1.2 . 2))))
  \clef "bass"
  r2 r8
  <fis, b, f!> \repeat unfold 2 q
  <fis, b, f!> \repeat unfold 7 q
  <fis, b, f!>[ \repeat unfold 2 q q]
}

upII = {
  s1*3
  R1*2
  <>_\markup \fontsize #-1.2 \whiteout "Die Tasten lautlos niederzudrücken"
  r2 r4 
  \set shapeNoteStyles = ##(do do do do do do do)
  <gis ais cis' fis'>4~
    -\tweak ParenthesesItem.font-size #0 \parenthesize ^\pp
  q1\fermata
  s1*2
  s2
}

downII = {
  \clef "bass"
  s1*3
  R1*2
  \set shapeNoteStyles = ##(do do do do do do do)
  r2 r4 <e, a, dis>~ q1_\fermata
  s1*2
  s2
}

pedal = {
  \howellPedalStyle
  
  s2 s8 s4.\sostenutoOn
  s1
  s1\sostenutoOff
  s1
  s8 
  \once \override Dynamics.PianoPedalBracket.shorten-pair = #'(0 . 1)
  s2..\sostenutoOn
  s1 
  s8\sostenutoOff 
  s2.. \sostenutoOn
  s2 s8\sostenutoOff s4.
  s1
  s2
}
  
\paper {
  indent = 19
  short-indent = 4
  ragged-last-bottom = ##f
  top-markup-spacing.padding = 1
  first-page-number = 20
  print-first-page-number = ##t
  top-margin = 20
  bottom-margin = 8
  last-bottom-spacing.padding = 8
  tagline = \markup \fill-line { "AMP-95611" \null }
}

\header {
  title = "8. Tiger"
  composer = \markup \center-column { "Henry Cowell" "(1928)" }
}


\score {
  \new GrandStaff
  <<
  	\new Staff = "up" 
  	  \with { 
  	  	shortInstrumentName = 
  	  	  \markup \hcenter-in #9 \center-column { "Rechte" "Hand" }
  	  }
  	  \upI
  	  
  	\new Staff = "down" 
  	  \with { 
  	  	shortInstrumentName = 
  	  	  \markup \hcenter-in #9 \center-column { "Linke" "Hand" } 
  	  }
  	  \downI
  	
  	
  	\new Staff = "upII" 
  	  \with { shortInstrumentName = \markup \hcenter-in #9 "Rechte" }
  	  \upII
  	  
  	\new Staff = "downII" 
  	  \with { shortInstrumentName = \markup \hcenter-in #9 "Linke" }
  	  \downII
  	  
  	\new Dynamics \pedal
  >>
  \layout {
    \context {
      \Voice
      \override Stem.details.cluster-thick-short = 0.3
    }
    \context {
      \Staff
      \RemoveAllEmptyStaves
      \numericTimeSignature
    }
    \context {
      \GrandStaff
      \consists #Cluster-span_stem_engraver
      \omit SystemStartBrace
      \override StaffGrouper.staff-staff-spacing.stretchability = 25
    }
    \context {
      \Score
      \omit BarNumber
    }
  }
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% sometimes usefull for cross-staff stems and clusters
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

pushNC =
\once \override NoteColumn.X-offset =
  #(lambda (grob)
    (let* ((p-c (ly:grob-parent grob X))
           (p-c-elts (ly:grob-object p-c 'elements))
           (stems
             (if (ly:grob-array? p-c-elts)
                 (filter
                   (lambda (elt)(grob::has-interface elt 'stem-interface))
                   (ly:grob-array->list p-c-elts))
                 #f))
           (stems-x-exts
             (if stems
                 (map
                   (lambda (stem)
                     (ly:grob-extent
                       stem
                       (ly:grob-common-refpoint grob stem X)
                       X))
                   stems)
                 '()))
           (sane-ext
             (filter interval-sane? stems-x-exts))
           (cars (map car sane-ext)))
    (if (pair? cars)
        (abs (- (apply max cars)  (apply min cars)))
        0)))
        
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% erase a shortInstrumentName
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

eraseShortInstrumentName =
  \context Staff
  \applyContext
    #(lambda (ctx)
      (ly:context-set-property! ctx 'shortInstrumentName ""))
      
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% extract from LSR 1098 "Indenting individual systems"
%% http://lsr.di.unimi.it/LSR/Item?id=1098
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%% HEADER %%%%%%%%
%
% this code was prompted by
% https://lists.gnu.org/archive/html/lilypond-user/2019-07/msg00139.html
% and offers a pseudoIndent hack suitable for general use

% keywords: 
% indent short-indent indentation system line 
% mid-score temporarily arbitrary individual single just only once
% coda margin
% mouse's tale acrostic mesostic spine

%%%%%%%% PSEUDOINDENT FUNCTIONS %%%%%%%%

% these two functions are for indenting individual systems
% - to left-indent a system, apply \pseudoIndent before the music continues
% - \pseudoIndents is similar, but lets you also indent on the right
% - both provide an option for changing that system's instrument names

% N.B. these functions 
% - assume application to non-ragged lines (generally the default)
% - include a manual \break to ensure application at line start 
% - misbehave if called more than once at the same line start

% the parameters of the (full) pseudoIndents function are:
% 1: name-tweaks
%      usually omitted; accepts replacement \markup for instrument names 
%      as an ordered list; starred elements leave their i-names unchanged.
% 2: left-indent 
%      additional left-indentation, in staff-space units; can be negative, 
%      but avoid a total indentation which implies (unsupported) stretching. 
% 3: right-indent 
%      amount of right-indentation, in staff-space units; can be negative. 
%      - not offered by the (reduced) pseudoIndent function


pseudoIndents = % inline alternative to a new \score, also with right-indent
#(define-music-function (parser location name-tweaks left-indent right-indent) 
  ((markup-list? '()) number? number?)
  (define (warn-stretched p1 p2) (ly:input-warning location (_
    " pseudoIndents ~s ~s is stretching staff; expect distorted layout") p1 p2))
  (let* ( 
    (narrowing (+ left-indent right-indent)) ; of staff implied by args
    
    (set-staffsymbol! (lambda (staffsymbol-grob) ; change staff to new width 
      (let* (
        (left-bound (ly:spanner-bound staffsymbol-grob LEFT))
        (left-moment (ly:grob-property left-bound 'when))
        (capo? (moment<=? left-moment ZERO-MOMENT)) ; in first system of score
        (layout (ly:grob-layout staffsymbol-grob))
        (lw (ly:output-def-lookup layout 'line-width)) ; debugging info
        (indent (ly:output-def-lookup layout (if capo? 'indent 'short-indent)))
        (old-stil (ly:staff-symbol::print staffsymbol-grob))
        (staffsymbol-x-ext (ly:stencil-extent old-stil X))
        ;; >=2.19.16's first system has old-stil already narrowed [2]
        ;; compensate for this (ie being not pristine) when calculating 
        ;; - old leftmost-x (its value is needed when setting so-called 'width) 
        ;; - the new width and position (via local variable narrowing_) 
        (ss-t (ly:staff-symbol-line-thickness staffsymbol-grob))
        (pristine? (<= 0 (car staffsymbol-x-ext) ss-t)) ; would expect half
        (leftmost-x (+ indent (if pristine? 0 narrowing))) 
        (narrowing_ (if pristine? narrowing 0)) ; uses 0 if already narrowed
        (old-width (+ (interval-length staffsymbol-x-ext) ss-t))
        (new-width (- old-width narrowing_))
        (new-rightmost-x (+ leftmost-x new-width)) ; and set! this immediately
        (junk (ly:grob-set-property! staffsymbol-grob 'width new-rightmost-x))
        (in-situ-stil (ly:staff-symbol::print staffsymbol-grob))
        (new-stil (ly:stencil-translate-axis in-situ-stil narrowing_ X))
       ;(new-stil (stencil-with-color new-stil red)) ; for when debugging
        (new-x-ext (ly:stencil-extent new-stil X)))
      (ly:grob-set-property! staffsymbol-grob 'stencil new-stil)
      (ly:grob-set-property! staffsymbol-grob 'X-extent new-x-ext)
      )))
    
    (set-X-offset! (lambda (margin-grob) ; move grob across to line start 
      (let* (
        (old (ly:grob-property-data margin-grob 'X-offset))                             
        (new (lambda (grob) (+ (if (procedure? old) (old grob) old) narrowing))))
      (ly:grob-set-property! margin-grob 'X-offset new))))
    
    (tweak-text! (lambda (i-name-grob mkup) ; tweak both instrumentname texts   
      (if (and (markup? mkup) (not (string=? (markup->string mkup) "*")))
      (begin 
        (ly:grob-set-property! i-name-grob 'long-text mkup)
        (ly:grob-set-property! i-name-grob 'text mkup)
        )))) ; else retain existing text 
    
    (install-narrowing (lambda (leftedge-grob) ; on staves, + adapt left margin
      (define (grob-name x) (assq-ref (ly:grob-property x 'meta) 'name))
      (let* (
        (sys (ly:grob-system leftedge-grob))
        (all-grobs (ly:grob-array->list (ly:grob-object sys 'all-elements)))
        (grobs-named (lambda (name)
          (filter (lambda (x) (eq? name (grob-name x))) all-grobs)))
        (first-leftedge-grob (list-ref (grobs-named 'LeftEdge) 0))
        (relsys-x-of (lambda (g) (ly:grob-relative-coordinate g sys X)))
        (leftedge-x (relsys-x-of first-leftedge-grob))
        (leftedged? (lambda (g) (= (relsys-x-of g) leftedge-x)))        
        (leftedged-ss (filter leftedged? (grobs-named 'StaffSymbol))))
      (if (eq? leftedge-grob first-leftedge-grob) ; ignore other leftedges [1]        
       (begin 
         (for-each set-staffsymbol! leftedged-ss)
         (for-each set-X-offset! (grobs-named 'SystemStartBar))
         (for-each set-X-offset! (grobs-named 'InstrumentName))
         (for-each tweak-text! (grobs-named 'InstrumentName) name-tweaks)
       ))))))
    
  (if (negative? narrowing) (warn-stretched left-indent right-indent)) 
  #{ % and continue anyway
    % ensure that these overrides are applied only at begin-of-line 
    \break % (but this does not exclude unsupported multiple application) 
    % give the spacing engine notice regarding the loss of width for music
    \once \override Score.LeftEdge.X-extent = #(cons narrowing narrowing)
    % discard line start region of staff and reassemble left-margin elements 
    \once \override Score.LeftEdge.after-line-breaking = #install-narrowing 
    % shift the system to partition the narrowing between left and right
    \overrideProperty Score.NonMusicalPaperColumn.line-break-system-details
    .X-offset #(- right-indent)
    % prevent a leftmost barnumber entering a stretched staff 
    \once \override Score.BarNumber.horizon-padding = #(max 1 (- 1 narrowing))
  #}))                                        

pseudoIndent = % for changing just left-indent 
#(define-music-function (parser location name-tweaks left-indent) 
  ((markup-list? '()) number?) 
  #{ 
    \pseudoIndents $name-tweaks $left-indent 0 
  #})

% [1] versions <2.19.1 can have end-of-line leftedges too  
%     - these were eliminated in issue 3761 
% [2] versions >=2.19.16: the first system behaves differently from the rest
%     - a side effect of issue 660 ?

%% [...] end LSR 1098



%%%%%%%%%%%%%%%%%%%%%%%%
%% from upcoming 2.23.0
%%%%%%%%%%%%%%%%%%%%%%%%

#(define spanner-bounds-break-status
  (lambda (spanner)
    (cons
     (ly:item-break-dir (ly:spanner-bound spanner LEFT))
     (ly:item-break-dir (ly:spanner-bound spanner RIGHT)))))

#(define-public unbroken-spanner?
  (lambda (spanner) (equal? '(0 . 0) (spanner-bounds-break-status spanner))))

#(define-public first-broken-spanner?
  (lambda (spanner) (equal? '(0 . -1) (spanner-bounds-break-status spanner))))

#(define-public middle-broken-spanner?
  (lambda (spanner) (equal? '(1 . -1) (spanner-bounds-break-status spanner))))

#(define-public end-broken-spanner?
  (lambda (spanner) (equal? '(1 . 0) (spanner-bounds-break-status spanner))))

#(define-public not-first-broken-spanner?
  (lambda (spanner) (positive? (car (spanner-bounds-break-status spanner)))))

#(define-public not-last-broken-spanner?
  (lambda (spanner) (negative? (cdr (spanner-bounds-break-status spanner)))))

#(define-public unbroken-or-last-broken-spanner?
  (lambda (spanner) (zero? (cdr (spanner-bounds-break-status spanner)))))

#(define-public unbroken-or-first-broken-spanner?
  (lambda (spanner) (zero? (car (spanner-bounds-break-status spanner)))))
  
%%%%%%%%%%%%%%%%%%%%%%%%
%% Tweak pedals
%%%%%%%%%%%%%%%%%%%%%%%%
%% AARGH!!
%% Is there no better method to get a pedal with a dashed line between the
%% glyphs "pedal.Ped" and "pedal.*"

howellPedalStyle = {
  \override Dynamics.SostenutoPedal.stencil =
    #(lambda (grob) 
      (grob-interpret-markup grob (make-musicglyph-markup "pedal.Ped")))
      
  
  \override Dynamics.PianoPedalBracket.edge-height = #'(0 . 0)
      
  \override Dynamics.PianoPedalBracket.style = #'dashed-line
  
  \override Dynamics.PianoPedalBracket.after-line-breaking =
    #(lambda (grob)
      (if (unbroken-or-last-broken-spanner? grob)
          (ly:grob-set-property! grob 'shorten-pair '(0 . 1.2))))
  
  \override Dynamics.PianoPedalBracket.stencil =
  #(grob-transformer 'stencil
    (lambda (grob orig)
      (if (unbroken-or-last-broken-spanner? grob)
          (ly:stencil-combine-at-edge
            orig
            X
            RIGHT
            (grob-interpret-markup grob (make-musicglyph-markup "pedal.*"))
            1)
          orig)))
          
  \override Dynamics.PianoPedalBracket.dash-fraction = 0.1
  
  \override Dynamics.PianoPedalBracket.dash-period = 8
}
\version "2.20.0"

#(define (note-column::main-extent grob)
"Return extent of the noteheads in the 'main column', (i.e. excluding any
suspended noteheads), or extent of the rest (if there are no heads)."
  (let* ((note-heads (ly:grob-object grob 'note-heads))
         ;; stem is currently not needed below, for now we let it in commented
         ;(stem (ly:grob-object grob 'stem))
         (rest (ly:grob-object grob 'rest)))
    (cond ((ly:grob-array? note-heads)
           (let (;; get the cdr from all note-heads-extents, where the car
                 ;; is zero
                 (n-h-right-coords
                   (filter-map
                     (lambda (n-h)
                       (let ((ext (ly:grob-extent n-h grob X)))
                          (and (= (car ext) 0) (cdr ext))))
                     (ly:grob-array->list note-heads))))
             ;; better be paranoid, find the max of n-h-right-coords and return
             ;; a pair with (cons 0 <max>)
             (cons 0.0 (reduce max 0 n-h-right-coords))))
          ((ly:grob? rest)
           (ly:grob-extent rest grob X))
          ;; better be paranoid again
          (else '(0 . 0)))))

#(define note-column-cluster
  (lambda (grob)
    (let* ((nhds-array (ly:grob-object grob 'note-heads))
           (nhds-list
             (if (ly:grob-array? nhds-array)
                 (ly:grob-array->list nhds-array)
                 #f)))
      (if nhds-list
          (let* ((staff-pos-list
                   (map
                     (lambda (nhd) (ly:grob-property nhd 'staff-position))
                     nhds-list))
                 (staff-space (ly:staff-symbol-staff-space grob))
                 (bottom-pos
                   (/ (* (apply min staff-pos-list) staff-space) 2))
                 (top-pos
                   (/ (* (apply max staff-pos-list) staff-space) 2))
                 (nc-width (note-column::main-extent grob))
                 (mid-nc
                   (interval-center nc-width))
                 (stem (ly:grob-object grob 'stem))
                 (stem-details
                   (ly:grob-property stem 'details))
                 (cluster-thick-short
                   (* staff-space
                      (assoc-get 'cluster-thick-short stem-details 0.54)))
                 (stem-y-attach 
                   (* staff-space
                      (cdr 
                        (ly:grob-property (car nhds-list) 'stem-attachment))))
                 (stem-dir (ly:grob-property stem 'direction))
                 (dur-log (ly:grob-property stem 'duration-log))
                 (layout (ly:grob-layout grob))
                 (blot (ly:output-def-lookup layout 'blot-diameter))
                 (line-thick (ly:output-def-lookup layout 'line-thickness 0.1))
                 (stem-thick (ly:grob-property stem 'thickness 1.3))
                 (thick 
                   (* (ly:grob-property 
                        grob 
                        'thickness 
                        (* stem-thick line-thick))
                      staff-space)))
   
            (ly:grob-set-property! stem 'avoid-note-head #t)
            (ly:grob-set-property! grob 'stencil
              (cond 
                ((= dur-log 0)
                  (ly:stencil-add
                    (ly:round-filled-box
                      (cons 0 thick)
                      (cons bottom-pos top-pos)
                      blot)
                    (ly:round-filled-box
                      (cons (- (cdr nc-width) thick) (cdr nc-width))
                      (cons bottom-pos top-pos)
                      blot)))
                ((= dur-log 1)
                   (ly:stencil-add
                    (stencil-with-color
                     (ly:round-filled-box
                       (cons 0 thick)
                       (cons 
                         (+ (/ staff-space 4) (- bottom-pos stem-y-attach))
                         (+ (/ staff-space 4) (- top-pos stem-y-attach)))
                       blot)
                      green)
                     (ly:round-filled-box
                       (cons (- (cdr nc-width) thick) (cdr nc-width))
                       (cons  
                         (- (+ bottom-pos stem-y-attach) (/ staff-space 4))
                         (- (+ top-pos stem-y-attach) (/ staff-space 4)))
                       blot)))
                (else
                  (let* ((x-left (- mid-nc (/ thick 2) cluster-thick-short))
                         (x-right (+ mid-nc (/ thick 2) cluster-thick-short))
                         (y-bottom-left
                           (+ (- bottom-pos thick stem-y-attach) 
                              (/ staff-space 4)))
                         (y-bottom-right
                           (- (+ bottom-pos thick stem-y-attach) 
                              (/ staff-space 4)))
                         (y-top-right
                           (- (+ top-pos thick stem-y-attach) 
                              (/ staff-space 4)))
                         (y-top-left
                           (+ (- top-pos thick stem-y-attach) 
                              (/ staff-space 4))))
                    (ly:make-stencil
                      `(polygon
                         ;; with 2.20.0 use
                         ',(list
                         ;; with newer versions:
                         ;,(list
                             x-left y-bottom-left
                             x-right y-bottom-right
                             x-right y-top-right
                             x-left y-top-left)
                          ,blot
                          #t) 
                      (cons x-left x-right)
                      (cons y-bottom-left y-top-right)))))))
          ;; else, do nothing
          '()))))

          
#(define (close-enough? x y)
  "Values are close enough to ignore the difference"
  (< (abs (- x y)) 0.0001))

#(define (extent-combine extents)
  "Combine a list of extents, return the minimum of the car and the maximum of
te cdr of all extents."
  (reduce interval-union '() extents))

#(define ((cluster-stem-connectable? ref root) stem)
  "Check if the @var{stem} is connectable to the @var{root}, done by comparing 
their horizontal positions and their @code{direction} property.
For whole Notes fall back to compare the extent of the related @code{NoteColumn}
grobs."
  (let* ((root-dur-log (ly:grob-property root 'duration-log))
         (root-x-ext
           (if (eqv? root-dur-log 0)
               (ly:grob-extent (ly:grob-parent root X) ref X)
               (ly:grob-extent root ref X)))
         (stem-x-ext
           (if (eqv? root-dur-log 0)
               (ly:grob-extent (ly:grob-parent stem X) ref X)
               (ly:grob-extent stem ref X))))
  ;; The root is always connectable to itself
  (or (eq? root stem)
      (and
       ;; Horizontal positions of the stems (or NoteColumns) must be almost the 
       ;; same
       (close-enough? (car root-x-ext) (car stem-x-ext))
       ;; The stem must be in the direction away from the root's notehead
       ;; Special case whole notes: always return #t
       (if (eqv? root-dur-log 0)
           #t
           (positive? (* (ly:grob-property root 'direction)
                         (- (car (ly:grob-extent stem ref Y))
                            (car (ly:grob-extent root ref Y))))))))))

#(define (cluster-stem-span-stencil span)
  "Connect stems if we have at least one stem connectable to the root.
@var{span} is the created target @code{grob}."
  (let* ((system (ly:grob-system span))
         (staff-space (ly:staff-symbol-staff-space span))
         (root (ly:grob-parent span X))
         (root-dir (ly:grob-property root 'direction 1))
         (root-duration-log (ly:grob-property root 'duration-log))
         (root-thick
           (ly:grob-property root 'thickness 1.3))
         (root-details
           (ly:grob-property root 'details))
         (cluster-thick-short
           (assoc-get 'cluster-thick-short root-details 0.54))
         (stems 
           (filter 
             (cluster-stem-connectable? system root)
             (ly:grob-object span 'stems)))
         (parent-ncs 
           (map
             (lambda (stem)
               (ly:grob-parent stem X))
             stems))
         (ncs-extents
           (map
              note-column::main-extent
              parent-ncs))
         ;; Use half width for half notes and longer
         (nc-x-width
           (interval-center
             (extent-combine ncs-extents)))
         (layout (ly:grob-layout root))
         (line-thick (ly:output-def-lookup layout 'line-thickness 0.1))
;(foo
;  (pretty-print
;    (list
;      staff-space
;      (ly:output-def-lookup layout 'staff-space)
;      (ly:output-def-lookup layout 'output-scale)
;      (ly:output-def-lookup (ly:grob-layout (car stems)) 'staff-space)
;      )
;    ))
         (half-used-thick
           (/ (* line-thick root-thick) 2))
         (blot (ly:output-def-lookup layout 'blot-diameter))
             )
             

    (if (= 2 (length stems))
        (let* (;; Get the Y-extents of all the original stems
               ;; For whole note stems fall back to Y-extents of their 
               ;; NoteColumn
               (y-extents 
                 (cond ((< root-duration-log 1)
                         (map
                           (lambda (nc) (ly:grob-extent nc system Y))
                           parent-ncs))
                       (else
                         (map 
                           (lambda (st)
                             (ly:grob-extent st system Y)) 
                           stems))))
               ;; For uppointing Stem accumulate their car, otherwise cdr,
               ;; for whole note stems use the center of their NoteColumn extent
               ;; to build a list. This list is used to determine bottom/top
               ;; values to print the cluster-stem lateron
               (stem-starts-ls
                 (cond ((< root-duration-log 1)
                         (list (interval-center (car y-extents))
                               (interval-center (last y-extents))))
                       (else
                         (if (positive? root-dir)
                             (map car y-extents)
                             (map cdr y-extents)))))
               (y-ext
                 (cons (car stem-starts-ls) (last stem-starts-ls)))
               (raw-stencil
                   (ly:round-filled-box
                     (interval-scale (cons (- half-used-thick) half-used-thick) 1)
                     y-ext 
                     blot))
               (stem-attach
                 (ly:grob-property
                   (car 
                     (ly:grob-array->list 
                       (ly:grob-object root 'note-heads)))
                   'stem-attachment)))

          ;; Hide root stem, i.e. the stem of the lowest connected note
          (set! (ly:grob-property root 'stencil) #f)
          ;; Draw a nice looking stem with rounded corners
          (cond 
            ;; whole notes
            ((= root-duration-log 0)
              (ly:stencil-add
                (ly:stencil-translate-axis
                  raw-stencil
                  (- half-used-thick nc-x-width)
                  X)
                (ly:stencil-translate-axis
                  raw-stencil
                  (- nc-x-width half-used-thick)
                  X)
                ))
            ;; half notes
            ((= root-duration-log 1)
              (ly:stencil-add
                raw-stencil
                (ly:round-filled-box 
                  (coord-translate
                    (cons (- half-used-thick) half-used-thick)
                    (* 2 root-dir (- half-used-thick nc-x-width)))
                  (cons 
                    (+ (car y-ext) (* -1 root-dir (cdr stem-attach)))
                    (+ (cdr y-ext) (* -1 root-dir (cdr stem-attach))))
                  blot)))
            ;; 4th and shorter
            (else
              (let* ((x-right (+ half-used-thick cluster-thick-short))
                     (x-left (- x-right))
                     (y-left-bottom
                       (+ (car y-ext) 
                          (if (negative? root-dir)
                              0
                              (- (cdr stem-attach)))))
                     (y-right-bottom
                       (+ (car y-ext) 
                          (if (negative? root-dir)
                              (cdr stem-attach)
                              0)))
                     (y-right-top
                       (+ (cdr y-ext)
                          (if (negative? root-dir)
                              (cdr stem-attach)
                              0)))
                     (y-left-top
                       (+ (cdr y-ext) 
                          (if (negative? root-dir)
                              0
                              (- (cdr stem-attach))))))
            (ly:stencil-translate-axis 
              (ly:make-stencil
                `(polygon
                   ;; with 2.20.0 use
                   ',(list
                   ;; with newer versions:
                   ;,(list
                      x-left  y-left-bottom
                      x-right y-right-bottom
                      x-right y-right-top
                      x-left  y-left-top 
                      
                      )
                    ,blot
                    #t) 
                (cons (- half-used-thick) half-used-thick)
                y-ext
                )
              (* -1 root-dir (- nc-x-width half-used-thick))
              X)
              ))))
        ;; Nothing to connect, don't draw the span
        #f)))

#(define ((make-cluster-stem-span! stems trans) root)
  "Create a stem span as a child of the cross-staff stem (the root)"
  (let ((span (ly:engraver-make-grob trans 'Stem '())))
    (ly:grob-set-parent! span X root)
    (set! (ly:grob-object span 'stems) stems)
    ;; Suppress positioning, the stem code is confused by this weird stem
    (set! (ly:grob-property span 'X-offset) 0)
    (set! (ly:grob-property span 'stencil) cluster-stem-span-stencil)))

#(define-public (cross-staff-connect stem)
  "Set cross-staff property of the stem to this function to connect it to
other stems automatically"
  #t)

#(define (stem-is-root? stem)
  "Check if automatic connecting of the stem was requested.  Stems connected
to cross-staff beams are cross-staff, but they should not be connected to
other stems just because of that."
  (eq? cross-staff-connect (ly:grob-property-data stem 'cross-staff)))

#(define (make-cluster-stem-spans! ctx stems trans)
  "Create stem spans for cross-staff stems"
  ;; Cannot do extensive checks here, just make sure there are at least
  ;; two stems at this musical moment
  (if (= 2 (length stems))
      (let ((roots (filter stem-is-root? stems)))
        (for-each (make-cluster-stem-span! stems trans) roots))))

#(define-public (Cluster-span_stem_engraver ctx)
  "Connect cross-staff stems to the stems above in the system"
  (let ((stems '()))
    (make-engraver
     ;; Record all stems with note-heads for the given moment
     (acknowledgers
      ((stem-interface trans grob source)
       (if (ly:grob-array? (ly:grob-object grob 'note-heads))
           (set! stems (cons grob stems)))))
     ;; Process stems and reset the stem list to empty
     ((process-acknowledged trans)
      (make-cluster-stem-spans! ctx stems trans)
      (set! stems '())))))

cluster =
#(define-music-function (cross-staff notes) ((boolean? #f) ly:music?)
  (_i "Create cross-staff stems")
  (if cross-staff
      #{
        \temporary \override Stem.cross-staff = #cross-staff-connect
        \temporary \override Flag.style = #'no-flag
        $notes
        \revert Stem.cross-staff
        \revert Flag.style
      #}
      #{
      	 \temporary \override NoteColumn.before-line-breaking = 
      	   #note-column-cluster
      	 $notes
      	 \revert NoteColumn.before-line-breaking
      #})
)

Reply via email to