Hi all,

Attached is my latest attempt to make progress toward an automatic
mechanism for n-parts that may share a staff or not.

The idea is that if the context property combineNext = ##t, then that part
can share a staff with the next part down. The engraver is supposed to set
keepAliveInterfaces = '() for all the staves except the ones corresponding
to the current part grouping. Then depending on line-breaking, staves will
be hidden according to remove-layers.

I have two issues currently:
1) How to initialize the engraver with a list of child staff contexts?
Grabbing them by acknowledging hara-kiri-group-spanner grobs means this
engraver can't affect which staves display on the first system, which isn't
ideal. Is there a better way?

2) ly:context-set-property! seems to work in the sense that the changed
value gets read back correctly, but it doesn't seem like it's affecting the
music output as I'd expect. Should I be having the engraver broadcast a
property change event or something? How would one even do that? Or is this
something to do with the timing of property changes happening over line
breaks?

Apologies for the less than elegant Scheme code. I would love some
feedback/assistance on how to improve this or approach it better.

Happy new year!
\version "2.19.82"

#(debug-enable 'debug)

unfrench = #'(
               bass-figure-interface
               chord-name-interface
               cluster-beacon-interface
               fret-diagram-interface
               lyric-syllable-interface
               note-head-interface
               tab-note-head-interface
               lyric-interface
               percent-repeat-item-interface
               percent-repeat-interface
               ;; need this, as stanza numbers are items, and appear only once.
               stanza-number-interface

               multi-measure-rest-interface
               )

% 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 'combineNext boolean? "Is it okay for this music to share a staff with the music in the next staff?")

%% modified from lily-library.scm
#(define (split-at-predicate pred lst)
   "Split LST into two lists at the first element that returns #t for
  (PRED element).  Return the two parts as a pair.
  Example: (split-at-predicate odd? '(0 2 3 2 0)) ==> ((0 2 3) . (2 0))"
   (let ((i (and (pair? lst)
                 (list-index pred
                   lst))))
     (if i
         (call-with-values
          (lambda () (split-at lst (1+ i)))
          cons)
         (list lst))))

#(define (rsplit pred lst)
   (let ((splitted (split-at-predicate pred lst)))
     (if (pair? (cdr splitted))
         (cons (car splitted) (rsplit pred (cdr splitted)))
         splitted)))

% #(display (rsplit (lambda (x) (not (cdr x))) '((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)))))))
                                 ))


                               ((process-music translator)
                                (let* (
                                        ; Build alist of index: combineNext value for solo staves
                                        (combine-which-parts (fold-right
                                                              (lambda (kv result)
                                                                (acons (car kv)
                                                                  (ly:context-property (cdr kv) 'combineNext)
                                                                  result))
                                                              '()
                                                              solo-staves))

                                        ; Split the list by which parts can be combined
                                        (split-alist (rsplit
                                                      (lambda (x) (not (cdr x)))
                                                      combine-which-parts))
                                        ; Then just keep the part indices
                                        (groups (map
                                                 (lambda (sublst)
                                                   (map caar sublst))
                                                 split-alist))
                                        ; 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)
                                  
                                  ; Some code to verify that the engraver is indeed modifying
                                  ; keepAliveInterfaces as intended
                                  (map (lambda (kv)
                                         (display
                                          (cons (car kv)
                                            (pair?
                                             (ly:context-property (cdr kv) 'keepAliveInterfaces))
                                            )))
                                    all-staves)
                                  ))
                               )))

\layout {
  \context {
    \Score
    keepAliveInterfaces = \unfrench
  }
}

\new StaffGroup \with {
  \consists \Staff_sharing_engraver
  combineNext = ##t
  \override VerticalAxisGroup.remove-empty = ##t
  % \consists Keep_alive_together_engraver
} <<
  \new Staff = "1" \with {
    sharingParts = #'(1)
    \override VerticalAxisGroup.remove-layer = 0
    shortInstrumentName = "1"
  } {
    R1*2
    \set Staff.combineNext = ##f
    R1*6
  }
  \new Staff = "2" \with {
    sharingParts = #'(2)
    \override VerticalAxisGroup.remove-layer = 0
    shortInstrumentName = "2"
  } {
    R1*6
    \set Staff.combineNext = ##f
    R1*2
  }
  \new Staff = "3" \with {
    sharingParts = #'(3)
    \override VerticalAxisGroup.remove-layer = 0
    shortInstrumentName = "3"
  } { R1*8 }
  \new Staff = "1+2" \with {
    sharingParts = #'(1 2)
    \override VerticalAxisGroup.remove-layer = 1
    shortInstrumentName = "1 2"
  } { R1*8 }
  \new Staff = "2+3" \with {
    sharingParts = #'(2 3)
    \override VerticalAxisGroup.remove-layer = 1
    shortInstrumentName = "2 3"
  } { R1*8 }
  \new Staff = "1+2+3" \with {
    sharingParts = #'(1 2 3)
    \override VerticalAxisGroup.remove-layer = 2
    shortInstrumentName = "1 2 3"
  } \repeat unfold 8 {
    R1 \break
  }
>>

% Define custom context properties (initialize) — these can just be defined in \with
% Catch changes to combineDown (process-music)
% Nice to have: check if both parts have MMRests, if yes assume combineDown
% Build part lists from combineDown status
% Push property change events into the stream for specific contexts:
% Unset keepAliveInterfaces to corresponding staves, set to #'() for all others
_______________________________________________
lilypond-user mailing list
lilypond-user@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to