\version "2.14.2" % Values are close enough to ignore the difference #(define (close-enough? x y) (< (abs (- x y)) 0.0001)) % Combine a list of extents #(define (extent-combine extents) (if (pair? (cdr extents)) (interval-union (car extents) (extent-combine (cdr extents))) (car extents))) % Workaround for Lilypond 2.14 - calculate X-extent without the flag #(define (stem-x-extent stem) (let* ((extent (ly:grob-extent stem stem X)) (layout (ly:grob-layout stem)) (thickness (ly:grob-property stem 'thickness)) (thickness-unit (ly:output-def-lookup layout 'line-thickness))) (cons (car extent) (+ (car extent) (* thickness thickness-unit))))) % Check if the stem is connectable to the stem span #(define ((stem-connectable? ref anchor) stem) (cond ((inf? (car (ly:grob-extent stem ref X))) #f) ((not (close-enough? (car (ly:grob-extent anchor ref X)) (car (ly:grob-extent stem ref X)))) #f) ((< 0 (* (ly:grob-property anchor 'direction) (- (car (ly:grob-extent anchor ref Y)) (car (ly:grob-extent stem ref Y))))) #f) (else #t))) % Connect stems if we have at least two visible stems and the anchor stem % is one of them #(define (stem-span-stencil span) (let* ((system (ly:grob-system span)) (anchor (ly:grob-parent span X)) (stems (filter (stem-connectable? system anchor) (ly:grob-object span 'stems)))) (if (and (pair? stems) (pair? (cdr stems)) (memq anchor stems)) (let* ((yextents (map (lambda (st) (ly:grob-extent st system Y)) stems)) (yextent (extent-combine yextents)) (layout (ly:grob-layout anchor)) (blot (ly:output-def-lookup layout 'blot-diameter))) ; Hide spanned stems, but only if it won't hide flags (map (lambda (st) (if (close-enough? (cdr (stem-x-extent st)) (cdr (ly:grob-extent st st X))) (set! (ly:grob-property st 'transparent) #t))) stems) (ly:round-filled-box (stem-x-extent anchor) yextent blot)) #f))) % Create a stem span as a child of the cross-staff stem (the anchor) #(define ((make-stem-span! stems trans) anchor) (let* ((span (ly:engraver-make-grob trans 'Stem '()))) (ly:grob-set-parent! span X anchor) (set! (ly:grob-object span 'stems) stems) (set! (ly:grob-property span 'X-offset) 0) (set! (ly:grob-property span 'stencil) stem-span-stencil))) % Create stem spans for cross-staff stems #(define (make-stem-spans! ctx stems trans) (if (and (pair? stems) (pair? (cdr stems))) (let* ((anchors (filter (lambda (st) (ly:grob-property st 'cross-staff)) stems))) (map (make-stem-span! stems trans) anchors)))) % Connect cross-staff stems to the stems above in the system #(define (Span_stem_engraver ctx) (let ((stems '())) `((acknowledgers (stem-interface . ,(lambda (trans grob source) (set! stems (cons grob stems))))) (process-acknowledged . ,(lambda (trans) (make-stem-spans! ctx stems trans) (set! stems '())))))) \layout { \context { \StaffGroup \consists #Span_stem_engraver } } \score { \new StaffGroup << \new Staff = "staffone" << \new Voice { \voiceOne c''4 } \new Voice { \override Stem #'cross-staff = ##t \voiceTwo c'4 } \new Voice { \voiceThree g'4 \stemDown c'4 } >> \new Staff = "stafftwo" << \new Voice { \clef bass \voiceOne g4 } \new Voice { \clef bass \voiceTwo e,4 } \new Voice { \clef bass \voiceThree d4 \override Stem #'cross-staff = ##t \stemDown f8 f8 } >> \new Staff = "staffthree" << \new Voice { \clef bass \voiceOne f4 } \new Voice { \clef bass \voiceTwo g,4 } \new Voice { \once \override Stem #'cross-staff = ##t \clef bass \voiceThree c4 \stemDown \autoBeamOff g8 g8 } >> >> \layout { } }