Updating this with cleaned up code and a clearer example in four parts. Realized a few things:
1) The context property changes are working as I expected and staves are correctly hiding or showing. There is no need to use remove-layers here.
2) When keepAliveInterfaces is set to '() in the first moment of a system, the new value only applies starting after the line break, so if the staff was alive on the previous system it will be kept alive on this system. The workaround is to apply changes to keepAliveInterfaces at a moment just before they are meant to take effect. This makes for awkward user input, but doing it automatically would require the engraver looking a moment into the future. Is looking ahead a feasible thing for an engraver to do? Should this be written as an iterator instead?
\version "2.19.82"
% From define-context-properties.scm
#(define (translator-property-description symbol type? description)
(if (not (and
(symbol? symbol)
(procedure? type?)
(string? description)))
(throw 'init-format-error))
(if (not (equal? #f (object-property symbol 'translation-doc)))
(ly:error (_ "symbol ~S redefined") symbol))
(set-object-property! symbol 'translation-type? type?)
(set-object-property! symbol 'translation-doc description)
(set! all-translation-properties (cons symbol all-translation-properties))
symbol)
#(translator-property-description 'sharingParts list? "List of consecutive ints, indices of parts sharing this staff.")
#(translator-property-description 'combineWithNext boolean? "Is it okay for this music to share a staff with the music in the next staff?")
#(define (segment pred lst)
"Segments a list into sublists, such that all elements satisfy pred
except the last element of each sublist."
(fold-right
(lambda (x y)
(if (pred x)
(cons (cons x (car y)) (cdr y))
(cons (list x) y)))
'(() . ())
lst))
% #(display (segment cdr '((1 . #f) (2 . #t) (3 . #t))))
Staff_sharing_engraver = #(lambda (ctx)
(let* ((all-staves '())
(solo-staves '()))
(make-engraver
(acknowledgers
((hara-kiri-group-spanner-interface
engraver grob source-engraver)
; We need an alist of child contexts, but getting them this way
; means the engraver can't operate on the first measure.
; Not sure what the proper way to get them is?
(let* (
(child-ctx (ly:translator-context source-engraver))
(child-id (ly:context-id child-ctx))
(child-parts (ly:context-property child-ctx 'sharingParts))
)
(set! all-staves (assoc-set! all-staves child-parts child-ctx))
; Build alist of just the staves for a single part.
(if (and (eq? 1 (length child-parts))
(not (assoc child-parts solo-staves)))
(set! solo-staves
(merge solo-staves
(acons child-parts child-ctx '())
(lambda (x y) (< (caar x) (caar y)))))))
))
((start-translation-timestep translator)
(let* (
; Build alist of index: combineWithNext value for solo staves
(combine-which-parts (fold-right
(lambda (kv result)
(acons (car kv)
(ly:context-property (cdr kv) 'combineWithNext)
result))
'()
solo-staves))
; Split the list by which parts can be combined
; Then just keep the part indices
(groups (map
(lambda (sublst)
(map caar sublst))
(segment cdr combine-which-parts)))
; Use the index lists to select staves
(live-ctxs (if (pair? (car groups))
(map
(lambda (k) (assoc-get k all-staves))
groups)
'()))
)
; Set keepAliveInterfaces = '() for all staves
(map (lambda (kv)
(ly:context-set-property! (cdr kv)
'keepAliveInterfaces '()))
all-staves)
; Set keepAliveInterfaces to the default for just the staves
; Corresponding to the current part combinations
(map (lambda (ctx)
(ly:context-unset-property ctx 'keepAliveInterfaces))
live-ctxs)
))
)))
\paper {
short-indent = 0.5\cm
}
\new StaffGroup \with {
\consists \Staff_sharing_engraver
combineWithNext = ##t
\override VerticalAxisGroup.remove-empty = ##t
\override VerticalAxisGroup.remove-first = ##t
} <<
\new Staff = "1+2" \with {
sharingParts = #'(1 2)
shortInstrumentName = "1 2"
keepAliveInterfaces = #'()
} \repeat unfold 8 {
c'8 8 8 8 8 8 8 8
}
\new Staff = "1+2+3" \with {
sharingParts = #'(1 2 3)
shortInstrumentName = "1 2 3"
keepAliveInterfaces = #'()
} \repeat unfold 8 {
c'8 8 8 8 8 8 8 8
}
\new Staff = "1+2+3+4" \with {
sharingParts = #'(1 2 3 4)
shortInstrumentName = "1 2 3 4"
keepAliveInterfaces = #'()
} {
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
c'8 8 8 8 8 8 8 8 \break
}
\new Staff = "1" \with {
sharingParts = #'(1)
shortInstrumentName = "1"
keepAliveInterfaces = #'()
} {
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
}
\new Staff = "2+3+4" \with {
sharingParts = #'(2 3 4)
shortInstrumentName = "2 3 4"
keepAliveInterfaces = #'()
} \repeat unfold 8 {
c'8 8 8 8 8 8 8 8
}
\new Staff = "2+3" \with {
sharingParts = #'(2 3)
shortInstrumentName = "2 3"
keepAliveInterfaces = #'()
} \repeat unfold 8 {
c'8 8 8 8 8 8 8 8
}
\new Staff = "2" \with {
sharingParts = #'(2)
shortInstrumentName = "2"
keepAliveInterfaces = #'()
} {
c'8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##t
c'8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8 8
c'8 8 8 8 8 8 8 8
}
\new Staff = "3+4" \with {
sharingParts = #'(3 4)
shortInstrumentName = "3 4"
keepAliveInterfaces = #'()
} \repeat unfold 8 {
c'8 8 8 8 8 8 8 8
}
\new Staff = "3" \with {
sharingParts = #'(3)
shortInstrumentName = "3"
keepAliveInterfaces = #'()
} {
c'8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##t
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##t
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##t
c'8 8 8 8 8 8 8 8
\set Staff.combineWithNext = ##f
c'8 8 8 8 8 8 8 8 8
}
\new Staff = "4" \with {
sharingParts = #'(4)
shortInstrumentName = "4"
keepAliveInterfaces = #'()
} \repeat unfold 8 { c'8 8 8 8 8 8 8 8 }
>>