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