[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lilypond-user
Subject:    Re: Attempt at divisi automating engraver
From:       Saul Tobin <saul.james.tobin () gmail ! com>
Date:       2018-12-31 1:34:49
Message-ID: CAPF+3FGCnUGWKqU4xqmEjbSH9m5vryrvgEZyLxk4C9Dipnv-GA () mail ! gmail ! com
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


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

On Sat, Dec 29, 2018 at 5:21 PM Saul Tobin <saul.james.tobin@gmail.com>
wrote:

> 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!
>

[Attachment #5 (text/html)]

<div dir="ltr"><div dir="ltr">Updating this with cleaned up code and a clearer \
example in four parts. Realized a few things:<div><br></div><div>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.</div><div><br></div><div>2) When \
keepAliveInterfaces is set to &#39;() 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?</div><div><br></div><div><div>\version \
&quot;2.19.82&quot;</div><div><br></div><div>% From \
define-context-properties.scm</div><div>#(define (translator-property-description \
symbol type? description)</div><div>   (if (not (and</div><div>             (symbol? \
symbol)</div><div>             (procedure? type?)</div><div>             (string? \
description)))</div><div>       (throw \
&#39;init-format-error))</div><div><br></div><div><br></div><div>   (if (not (equal? \
#f (object-property symbol &#39;translation-doc)))</div><div>       (ly:error (_ \
&quot;symbol ~S redefined&quot;) symbol))</div><div><br></div><div>   \
(set-object-property! symbol &#39;translation-type? type?)</div><div>   \
(set-object-property! symbol &#39;translation-doc description)</div><div>   (set! \
all-translation-properties (cons symbol all-translation-properties))</div><div>   \
symbol)</div><div><br></div><div><br></div><div>#(translator-property-description \
&#39;sharingParts list? &quot;List of consecutive ints, indices of parts sharing this \
staff.&quot;)</div><div>#(translator-property-description &#39;combineWithNext \
boolean? &quot;Is it okay for this music to share a staff with the music in the next \
staff?&quot;)</div><div><br></div><div>#(define (segment pred lst)</div><div>   \
&quot;Segments a list into sublists, such that all elements satisfy pred</div><div>   \
except the last element of each sublist.&quot;</div><div>   (fold-right</div><div>    \
(lambda (x y)</div><div>      (if (pred x)</div><div>          (cons (cons x (car y)) \
(cdr y))</div><div>          (cons (list x) y)))</div><div>    &#39;(() . \
())</div><div>    lst))</div><div><br></div><div>% #(display (segment cdr &#39;((1 . \
#f) (2 . #t) (3 . #t))))</div><div><br></div><div>Staff_sharing_engraver = #(lambda \
(ctx)</div><div>                            (let* ((all-staves &#39;())</div><div>    \
(solo-staves &#39;()))</div><div>                              \
(make-engraver</div><div>                               (acknowledgers</div><div>     \
((hara-kiri-group-spanner-interface</div><div>                                  \
engraver grob source-engraver)</div><div>                                 ; We need \
an alist of child contexts, but getting them this way</div><div>                      \
; means the engraver can&#39;t operate on the first measure.</div><div>               \
; Not sure what the proper way to get them is?</div><div>                             \
(let* (</div><div>                                         (child-ctx \
(ly:translator-context source-engraver))</div><div>                                   \
(child-id (ly:context-id child-ctx))</div><div>                                       \
(child-parts (ly:context-property child-ctx &#39;sharingParts))</div><div>            \
)</div><div>                                   (set! all-staves (assoc-set! \
all-staves child-parts child-ctx))</div><div><br></div><div>                          \
; Build alist of just the staves for a single part.</div><div>                        \
(if (and (eq? 1 (length child-parts))</div><div>                                      \
(not (assoc child-parts solo-staves)))</div><div>                                     \
(set! solo-staves</div><div>                                             (merge \
solo-staves</div><div>                                               (acons \
child-parts child-ctx &#39;())</div><div>                                             \
(lambda (x y) (&lt; (caar x) (caar y)))))))</div><div>                                \
))</div><div><br></div><div><br></div><div>                               \
((start-translation-timestep translator)</div><div>                                \
(let* (</div><div>                                        ; Build alist of index: \
combineWithNext value for solo staves</div><div>                                      \
(combine-which-parts (fold-right</div><div>                                           \
(lambda (kv result)</div><div>                                                        \
(acons (car kv)</div><div>                                                            \
(ly:context-property (cdr kv) &#39;combineWithNext)</div><div>                        \
result))</div><div>                                                              \
&#39;()</div><div>                                                              \
solo-staves))</div><div><br></div><div>                                        ; \
Split the list by which parts can be combined</div><div>                              \
; Then just keep the part indices</div><div>                                        \
(groups (map</div><div>                                                 (lambda \
(sublst)</div><div>                                                   (map caar \
sublst))</div><div>                                                 (segment cdr \
combine-which-parts)))</div><div>                                        ; Use the \
index lists to select staves</div><div>                                        \
(live-ctxs (if (pair? (car groups))</div><div>                                        \
(map</div><div>                                                        (lambda (k) \
(assoc-get k all-staves))</div><div>                                                  \
groups)</div><div>                                                       \
&#39;()))</div><div>                                        )</div><div>              \
; Set keepAliveInterfaces = &#39;() for all staves</div><div>                         \
(map (lambda (kv)</div><div>                                         \
(ly:context-set-property! (cdr kv)</div><div>                                         \
&#39;keepAliveInterfaces &#39;()))</div><div>                                    \
all-staves)</div><div>                                  ; Set keepAliveInterfaces to \
the default for just the staves</div><div>                                  ; \
Corresponding to the current part combinations</div><div>                             \
(map (lambda (ctx)</div><div>                                         \
(ly:context-unset-property ctx &#39;keepAliveInterfaces))</div><div>                  \
live-ctxs)</div><div>                                  </div><div>                    \
))</div><div>                               )))</div><div><br></div><div>\paper \
{</div><div>  short-indent = 0.5\cm</div><div>}</div><div><br></div><div>\new \
StaffGroup \with {</div><div>  \consists \Staff_sharing_engraver</div><div>  \
combineWithNext = ##t</div><div>  \override VerticalAxisGroup.remove-empty = \
##t</div><div>  \override VerticalAxisGroup.remove-first = ##t</div><div>} \
&lt;&lt;</div><div>    \new Staff = &quot;1+2&quot; \with {</div><div>    \
sharingParts = #&#39;(1 2)</div><div>    shortInstrumentName = &quot;1 \
2&quot;</div><div>    keepAliveInterfaces = #&#39;()</div><div>  } \repeat unfold 8 \
{</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>  }</div><div>  \new Staff = \
&quot;1+2+3&quot; \with {</div><div>    sharingParts = #&#39;(1 2 3)</div><div>    \
shortInstrumentName = &quot;1 2 3&quot;</div><div>    keepAliveInterfaces = \
#&#39;()</div><div>  } \repeat unfold 8 {</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>  }</div><div>  \new Staff = &quot;1+2+3+4&quot; \with {</div><div>    \
sharingParts = #&#39;(1 2 3 4)</div><div>    shortInstrumentName = &quot;1 2 3 \
4&quot;</div><div>    keepAliveInterfaces = #&#39;()</div><div>  } {</div><div>    \
c&#39;8 8 8 8 8 8 8 8 \break</div><div>    c&#39;8 8 8 8 8 8 8 8 \break</div><div>    \
c&#39;8 8 8 8 8 8 8 8 \break</div><div>    c&#39;8 8 8 8 8 8 8 8 \break</div><div>    \
c&#39;8 8 8 8 8 8 8 8 \break</div><div>    c&#39;8 8 8 8 8 8 8 8 \break</div><div>    \
c&#39;8 8 8 8 8 8 8 8 \break</div><div>    c&#39;8 8 8 8 8 8 8 8 \break</div><div>  \
}</div><div>  \new Staff = &quot;1&quot; \with {</div><div>    sharingParts = \
#&#39;(1)</div><div>    shortInstrumentName = &quot;1&quot;</div><div>    \
keepAliveInterfaces = #&#39;()</div><div>  } {</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>  \
c&#39;8 8 8 8 8 8 8</div><div>    \set Staff.combineWithNext = ##f</div><div>    \
c&#39;8 8 8 8 8 8 8 8 8</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>    c&#39;8 8 8 \
8 8 8 8 8</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>  }</div><div>  \new Staff = \
&quot;2+3+4&quot; \with {</div><div>    sharingParts = #&#39;(2 3 4)</div><div>    \
shortInstrumentName = &quot;2 3 4&quot;</div><div>    keepAliveInterfaces = \
#&#39;()</div><div>  } \repeat unfold 8 {</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>  }</div><div>  \new Staff = &quot;2+3&quot; \with {</div><div>    \
sharingParts = #&#39;(2 3)</div><div>    shortInstrumentName = &quot;2 \
3&quot;</div><div>    keepAliveInterfaces = #&#39;()</div><div>  } \repeat unfold 8 \
{</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>  }</div><div>  \new Staff = \
&quot;2&quot; \with {</div><div>    sharingParts = #&#39;(2)</div><div>    \
shortInstrumentName = &quot;2&quot;</div><div>    keepAliveInterfaces = \
#&#39;()</div><div>  } {</div><div>    c&#39;8 8 8 8 8 8 8</div><div>    c&#39;8 8 8 \
8 8 8 8 8</div><div>    \set Staff.combineWithNext = ##f</div><div>    c&#39;8 8 8 8 \
8 8 8 8</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>    \set Staff.combineWithNext \
= ##t</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##f</div><div>    c&#39;8 8 8 8 8 8 8 8 \
8</div><div>    c&#39;8 8 8 8 8 8 8 8</div><div>  }</div><div>    \new Staff = \
&quot;3+4&quot; \with {</div><div>    sharingParts = #&#39;(3 4)</div><div>    \
shortInstrumentName = &quot;3 4&quot;</div><div>    keepAliveInterfaces = \
#&#39;()</div><div>  } \repeat unfold 8 {</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>  }</div><div>  \new Staff = &quot;3&quot; \with {</div><div>    \
sharingParts = #&#39;(3)</div><div>    shortInstrumentName = &quot;3&quot;</div><div> \
keepAliveInterfaces = #&#39;()</div><div>  } {</div><div>    c&#39;8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##f</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##t</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##f</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##t</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##f</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##t</div><div>    c&#39;8 8 8 8 8 8 8 \
8</div><div>    \set Staff.combineWithNext = ##f</div><div>    c&#39;8 8 8 8 8 8 8 8 \
8</div><div>  }</div><div>  \new Staff = &quot;4&quot; \with {</div><div>    \
sharingParts = #&#39;(4)</div><div>    shortInstrumentName = &quot;4&quot;</div><div> \
keepAliveInterfaces = #&#39;()</div><div>  } \repeat unfold 8 { c&#39;8 8 8 8 8 8 8 8 \
}</div><div>&gt;&gt;</div></div></div></div><br><div class="gmail_quote"><div \
dir="ltr">On Sat, Dec 29, 2018 at 5:21 PM Saul Tobin &lt;<a \
href="mailto:saul.james.tobin@gmail.com">saul.james.tobin@gmail.com</a>&gt; \
wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px \
0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">Hi \
all,<div><br></div><div>Attached is my latest attempt to make progress toward an \
automatic mechanism for n-parts that may share a staff or \
not.</div><div><br></div><div>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 = &#39;() 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.</div><div><br></div><div>I have two issues \
currently:</div><div>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&#39;t affect which staves display on the first system, which isn&#39;t \
ideal. Is there a better way?</div><div><br></div><div>2) ly:context-set-property! \
seems to work in the sense that the changed value gets read back correctly, but it \
doesn&#39;t seem like it&#39;s affecting the music output as I&#39;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?</div><div><br></div><div>Apologies for the less than \
elegant Scheme code. I would love some feedback/assistance on how to improve this or \
approach it better.</div><div><br></div><div>Happy new year!</div></div> \
</blockquote></div>



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


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic