Hi all,

I posted some earlier versions of this Scheme engraver to the user list. It
provides an intuitive user interface to control whether shared or separate
staves are printed, and should work for combinations of 4 or more parts.
The examples are in the tests.ly file attached, while the engraver code is
in the divisi_engraver.ly file.

This engraver lives at the StaffGroup level and during process-music reads
from one context property, then writes to keepAliveInterfaces of each child
context. Because child contexts are evaluated first for process-music, in
order to get this to work I needed to patch Axis_group_engraver so that it
will wait to read keepAliveInterfaces and use the updated value.

Currently, Axis_group_engraver reads keepAliveInterfaces into an internal
variable during process-music, then uses the stored value during
acknowledge-grob. In the patched version, it waits to read
keepAliveInterfaces during acknowledge-grob. Reading the property every
grob is probably a performance hit, but I don't know how big.

Saul
\version "2.19.82"
\include "divisi_engraver.ly"

\paper {
  short-indent = 0.5\cm
}

\layout {
  \context {
    \StaffGroup
    \consists \Condense_staves_engraver
  }
  \context {
    \Staff
    \override VerticalAxisGroup.remove-empty = ##t
    \override VerticalAxisGroup.remove-first = ##t
    keepAliveInterfaces = #'()
  }
}


% TWO PARTS

global = {
  s1
  \break
  s1
}

I = {
  \context Staff = "1" { \set Staff.combineWithNext = ##f }
  c'4 4 4 4
  \context Staff = "1" { \set Staff.combineWithNext = ##t }
  c'4 4 4 4
}

II = {
  e2 2
  2 2
}

\new StaffGroup <<
  \new Staff = "1+2" \with {
    sharingParts = #'(1 2)
    instrumentName = "1 2"
    shortInstrumentName = "1 2"
  } << \global \I \II >>
  \new Staff = "1" \with {
    sharingParts = #'(1)
    instrumentName = "1"
    shortInstrumentName = "1"
  } << \global \I >>
  \new Staff = "2" \with {
    sharingParts = #'(2)
    instrumentName = "2"
    shortInstrumentName = "2"
  } << \global \II >>
>>

% FOUR PARTS

global = {
  s1
  \break
  s1
  \break
  s1
  \break
  s1
  \break
  s1
  \break
  s1
  \break
  s1
  \break
  s1
}

I = {
  \context Staff = "1" { \set Staff.combineWithNext = ##t }
  c'4 4 4 4
  c'4 4 4 4
  c'4 4 4 4
  c'4 4 4 4
  \context Staff = "1" { \set Staff.combineWithNext = ##f }
  c'4 4 4 4
  c'4 4 4 4
  c'4 4 4 4
  c'4 4 4 4
}

II = {
  \context Staff = "2" { \set Staff.combineWithNext = ##t }
  a4 4 4 4
  a4 4 4 4
  \context Staff = "2" { \set Staff.combineWithNext = ##f }
  a4 4 4 4
  a4 4 4 4
  \context Staff = "2" { \set Staff.combineWithNext = ##t }
  a4 4 4 4
  a4 4 4 4
  \context Staff = "2" { \set Staff.combineWithNext = ##f }
  a4 4 4 4
  a4 4 4 4
}

III = {
  \context Staff = "3" { \set Staff.combineWithNext = ##t }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##f }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##t }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##f }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##t }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##f }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##t }
  f4 4 4 4
  \context Staff = "3" { \set Staff.combineWithNext = ##f }
  f4 4 4 4
}

IV = {
  \repeat unfold 8 { d4 4 4 4 }
}

\new StaffGroup <<
  \new Staff = "1+2+3+4" \with {
    sharingParts = #'(1 2 3 4)
    instrumentName = "1 2 3 4"
    shortInstrumentName = "1 2 3 4"
  } << \global \I \II \III \IV >>
  \new Staff = "1+2+3" \with {
    sharingParts = #'(1 2 3)
    instrumentName = "1 2 3"
    shortInstrumentName = "1 2 3"
  } << \global \I \II \III >>
  \new Staff = "1+2" \with {
    sharingParts = #'(1 2)
    instrumentName = "1 2"
    shortInstrumentName = "1 2"
  } << \global \I \II >>
  \new Staff = "1" \with {
    sharingParts = #'(1)
    instrumentName = "1"
    shortInstrumentName = "1"
  } << \global \I >>
  \new Staff = "2+3+4" \with {
    sharingParts = #'(2 3 4)
    instrumentName = "2 3 4"
    shortInstrumentName = "2 3 4"
  } << \global \II \III \IV >>
  \new Staff = "2+3" \with {
    sharingParts = #'(2 3)
    instrumentName = "2 3"
    shortInstrumentName = "2 3"
  } << \global \II \III >>
  \new Staff = "2" \with {
    sharingParts = #'(2)
    instrumentName = "2"
    shortInstrumentName = "2"
  } << \global \II >>
  \new Staff = "3+4" \with {
    sharingParts = #'(3 4)
    instrumentName = "3 4"
    shortInstrumentName = "3 4"
  } << \global \III \IV >>
  \new Staff = "3" \with {
    sharingParts = #'(3)
    instrumentName = "3"
    shortInstrumentName = "3"
  } << \global \III >>
  \new Staff = "4" \with {
    sharingParts = #'(4)
    instrumentName = "4"
    shortInstrumentName = "4"
  } << \global \IV >>
>>
  

Attachment: axis_patch.patch
Description: Binary data

\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 (and (pred x) (pair? y))
        (cons (cons x (car y)) (cdr y))
        (cons (list x) y)))
    '()
    lst))

% #(display (segment cdr '((1 . #f) (2 . #f) (3 . #f))))

#(define (proper-subset? x y)
   "Is y a proper subset of x?"
   (and (every 
         (lambda (x) x) 
         (map 
          (lambda (ely) (member ely x))
          y))
        (< (length y) (length x))))

Condense_staves_engraver =
#(lambda (ctx)
   (let* ((all-hk-spanners '())
          (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-parts
                (ly:context-property child-ctx 'sharingParts)))
          (set! all-staves
                (assoc-set! all-staves child-parts child-ctx))
          (set! all-hk-spanners
                (assoc-set! all-hk-spanners child-parts grob))

          ; 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: 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? groups)
                              (map
                               (lambda (k) (assoc-get k all-staves))
                               groups)
                              '()))
               )
         (display (ly:context-current-moment ctx))
         ; (display live-ctxs)
         (display 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)))
      
      ((finalize translator)
       (let* ((get-subgroups (lambda (me)
                               (filter
                                (lambda (them)
                                  (proper-subset? (car me) (car them)))
                                all-hk-spanners)))
              (set-my-enemies (lambda (me)
                                (map
                                 (lambda (enemy)
                                   (ly:pointer-group-interface::add-grob
                                    (cdr me) 'make-dead-when (cdr enemy)))
                                 (get-subgroups me)))))
         (map set-my-enemies all-hk-spanners)
         )))))


_______________________________________________
lilypond-devel mailing list
lilypond-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-devel

Reply via email to