engraver to change staff name based on visibility of related staff?

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

engraver to change staff name based on visibility of related staff?

Shevek
Following up on an aspect of the discussion on parts sharing a staff (http://lilypond.1069038.n5.nabble.com/parts-sharing-a-staff-tt203873.html).

I've spent quite a few hours over the last day trying to figure out how engravers work and how I might take a crack at writing something to automatically handle changing Staff names for divisi staves. I feel like I've understood most of what I've read, but I'm still totally befuddled. This seems like a difficult problem because:

1) It's completely opaque to me how one would check if a Staff is visible or not on a given system. The hara-kiri-group-spanner, as far as I can tell, does its work without telling anybody about it. There's not like a boolean function you can call to find out if a context will be visible.

2) One needs to do something in one context depending on what happens in a different context. Most of the example engravers just do something in one context. Keep_alive_together_engraver collects grobs from different contexts and tells them about each other, but then relies on on them to figure out what to do with that information. That doesn't seem adequate here.

3) Such an engraver would need to step through the score by system, after breaking, rather than event by event, and it might need to be able to backtrack to insert an event to change the instrument name prior to the most recent system break. The code and documentation I've read has not given me any insight into how one might do such a thing.

I'd love some feedback and guidance.
Reply | Threaded
Open this post in threaded view
|

Re: engraver to change staff name based on visibility of related staff?

Thomas Morley-2
2017-06-16 23:47 GMT+02:00 Shevek <[hidden email]>:

> Following up on an aspect of the discussion on parts sharing a staff
> (http://lilypond.1069038.n5.nabble.com/parts-sharing-a-staff-tt203873.html).
>
> I've spent quite a few hours over the last day trying to figure out how
> engravers work and how I might take a crack at writing something to
> automatically handle changing Staff names for divisi staves. I feel like
> I've understood most of what I've read, but I'm still totally befuddled.
> This seems like a difficult problem because:
>
> 1) It's completely opaque to me how one would check if a Staff is visible or
> not on a given system. The hara-kiri-group-spanner, as far as I can tell,
> does its work without telling anybody about it. There's not like a boolean
> function you can call to find out if a context will be visible.
>
> 2) One needs to do something in one context depending on what happens in a
> different context. Most of the example engravers just do something in one
> context. Keep_alive_together_engraver collects grobs from different contexts
> and tells them about each other, but then relies on on them to figure out
> what to do with that information. That doesn't seem adequate here.
>
> 3) Such an engraver would need to step through the score by system, after
> breaking, rather than event by event, and it might need to be able to
> backtrack to insert an event to change the instrument name prior to the most
> recent system break. The code and documentation I've read has not given me
> any insight into how one might do such a thing.
>
> I'd love some feedback and guidance.



Hi,

I doubt an engraver will ever work for this purpose. As far as I
understand an engraver usually puts in stuff far too early. You can
assign a procedure to a grob via 'after-line-breaking, but you will
not be able to access whatever the procedure returns _in_ the
engraver.
I may be proven wrong, though.

Nevertheless, I tried a different approach:

\version "2.19.62"

%% NB
%% It's a proof of concept, nothing more!!
%% Currently it will fail for various reasons with extended examples
#(define change-instr-names
  (lambda (grob)
    (let* ((sys (ly:grob-system grob))
           (sys-all-elts (ly:grob-object sys 'all-elements))
           (sys-all-elts-list
             (if (ly:grob-array? sys-all-elts)
                 (ly:grob-array->list sys-all-elts)
                 '()))
           ;; get all VerticalAxisGroup-grobs per System
           (vertical-axis-group-list
             (filter
               (lambda (g)
                 (grob::has-interface g 'hara-kiri-group-spanner-interface))
               sys-all-elts-list))
           ;; get all InstrumentName-grobs per System
           (instrument-names-list
             (filter
               (lambda (g)
                 (grob::has-interface g 'system-start-text-interface))
               sys-all-elts-list))
           ;; construct a nested list like
           ;;   ((#t #<Grob InstrumentName >)
           ;;    (#f #<Grob InstrumentName >))
           ;; the boolean indicates whether the Staff is alive, derived from
           ;; VerticalAxisGroup.Y-extent
           (alive?-instr-list
             (map
               (lambda (v i)
                 (list (interval-sane? (ly:grob-property v 'Y-extent)) i))
               vertical-axis-group-list
               instrument-names-list)))

           ;(pretty-print alive?-instr-list)

      ;; First constructing a list and then fragmenting it in various ways is
      ;; not elegant, to say the least.
      ;; Just a proof of concept...
      (if (any not (map car alive?-instr-list))
          (let* ((instr-to-set
                   (remove
                     (lambda (e)
                       (not (car e)))
                     alive?-instr-list)))
            (ly:grob-set-property! (cadar instr-to-set) 'text
              (make-column-markup
                (map
                  (lambda (arg)
                    (ly:grob-property arg 'text))
                  (map cadr alive?-instr-list)))))))))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Example
%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\layout {
  \context {
      \Staff
      \override VerticalAxisGroup.after-line-breaking = #change-instr-names
    \RemoveEmptyStaves
  }
}

<<
  \new Staff \with { instrumentName = "One" shortInstrumentName = "one" }
    { R1 \break c \break c }
  \new Staff \with { instrumentName = "Two" shortInstrumentName = "two" }
    { c1 R c }
>>


Cheers,
  Harm

_______________________________________________
lilypond-devel mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-devel
Reply | Threaded
Open this post in threaded view
|

Re: engraver to change staff name based on visibility of related staff?

Shevek
Hi,

Finally getting a chance to play around with this. Thanks for writing up
this proof of concept! I'm learning a lot.

A few questions:

1) When you build alive?-instr-list you seem to assume a correspondence
between vertical-axis-group-list and instrument-names-list. But is that
actually guaranteed? Is the list of all grobs ordered that way to begin
with, and does filter preserve that ordering?

2) Is there a way to check if two VerticalAxisGroups belong to the same
StaffGroup, for instance? What I want to do is modify this so for each
staff with the callback defined, it only looks at the other staves within
the same lowest-level staff grouping. That way, it could be used in a score
with multiple groups of staves. My first shot at just grabbing the related
staves was:

(filter
 (lambda (x)
   (equal? (ly:grob-parent grob 1) (ly:grob-parent x 1)))
 vertical-axis-group-list)

But it seems all VerticalAlignments are the same or something? Hopefully
it's sort of clear what I'm trying to do?

Thanks!

On Sat, Jun 17, 2017 at 3:42 AM, Thomas Morley <[hidden email]>
wrote:

> 2017-06-16 23:47 GMT+02:00 Shevek <[hidden email]>:
> > Following up on an aspect of the discussion on parts sharing a staff
> > (http://lilypond.1069038.n5.nabble.com/parts-sharing-a-
> staff-tt203873.html).
> >
> > I've spent quite a few hours over the last day trying to figure out how
> > engravers work and how I might take a crack at writing something to
> > automatically handle changing Staff names for divisi staves. I feel like
> > I've understood most of what I've read, but I'm still totally befuddled.
> > This seems like a difficult problem because:
> >
> > 1) It's completely opaque to me how one would check if a Staff is
> visible or
> > not on a given system. The hara-kiri-group-spanner, as far as I can tell,
> > does its work without telling anybody about it. There's not like a
> boolean
> > function you can call to find out if a context will be visible.
> >
> > 2) One needs to do something in one context depending on what happens in
> a
> > different context. Most of the example engravers just do something in one
> > context. Keep_alive_together_engraver collects grobs from different
> contexts
> > and tells them about each other, but then relies on on them to figure out
> > what to do with that information. That doesn't seem adequate here.
> >
> > 3) Such an engraver would need to step through the score by system, after
> > breaking, rather than event by event, and it might need to be able to
> > backtrack to insert an event to change the instrument name prior to the
> most
> > recent system break. The code and documentation I've read has not given
> me
> > any insight into how one might do such a thing.
> >
> > I'd love some feedback and guidance.
>
>
>
> Hi,
>
> I doubt an engraver will ever work for this purpose. As far as I
> understand an engraver usually puts in stuff far too early. You can
> assign a procedure to a grob via 'after-line-breaking, but you will
> not be able to access whatever the procedure returns _in_ the
> engraver.
> I may be proven wrong, though.
>
> Nevertheless, I tried a different approach:
>
> \version "2.19.62"
>
> %% NB
> %% It's a proof of concept, nothing more!!
> %% Currently it will fail for various reasons with extended examples
> #(define change-instr-names
>   (lambda (grob)
>     (let* ((sys (ly:grob-system grob))
>            (sys-all-elts (ly:grob-object sys 'all-elements))
>            (sys-all-elts-list
>              (if (ly:grob-array? sys-all-elts)
>                  (ly:grob-array->list sys-all-elts)
>                  '()))
>            ;; get all VerticalAxisGroup-grobs per System
>            (vertical-axis-group-list
>              (filter
>                (lambda (g)
>                  (grob::has-interface g 'hara-kiri-group-spanner-
> interface))
>                sys-all-elts-list))
>            ;; get all InstrumentName-grobs per System
>            (instrument-names-list
>              (filter
>                (lambda (g)
>                  (grob::has-interface g 'system-start-text-interface))
>                sys-all-elts-list))
>            ;; construct a nested list like
>            ;;   ((#t #<Grob InstrumentName >)
>            ;;    (#f #<Grob InstrumentName >))
>            ;; the boolean indicates whether the Staff is alive, derived
> from
>            ;; VerticalAxisGroup.Y-extent
>            (alive?-instr-list
>              (map
>                (lambda (v i)
>                  (list (interval-sane? (ly:grob-property v 'Y-extent)) i))
>                vertical-axis-group-list
>                instrument-names-list)))
>
>            ;(pretty-print alive?-instr-list)
>
>       ;; First constructing a list and then fragmenting it in various ways
> is
>       ;; not elegant, to say the least.
>       ;; Just a proof of concept...
>       (if (any not (map car alive?-instr-list))
>           (let* ((instr-to-set
>                    (remove
>                      (lambda (e)
>                        (not (car e)))
>                      alive?-instr-list)))
>             (ly:grob-set-property! (cadar instr-to-set) 'text
>               (make-column-markup
>                 (map
>                   (lambda (arg)
>                     (ly:grob-property arg 'text))
>                   (map cadr alive?-instr-list)))))))))
>
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%
> %% Example
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%
>
> \layout {
>   \context {
>       \Staff
>       \override VerticalAxisGroup.after-line-breaking =
> #change-instr-names
>     \RemoveEmptyStaves
>   }
> }
>
> <<
>   \new Staff \with { instrumentName = "One" shortInstrumentName = "one" }
>     { R1 \break c \break c }
>   \new Staff \with { instrumentName = "Two" shortInstrumentName = "two" }
>     { c1 R c }
> >>
>
>
> Cheers,
>   Harm
>
_______________________________________________
lilypond-devel mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-devel
Reply | Threaded
Open this post in threaded view
|

Re: engraver to change staff name based on visibility of related staff?

Shevek
Making some progress on this. I've managed to figure out how to use ly:grob-get-vertical-axis-group-index to only look at the current Staff and the next one below. That's clunkier than finding the VerticalAxisGroups that belong to the same StaffGroup, but it's a start.

My problem now is finding a way to grab the InstrumentName grob that belongs to the current Staff, not just the list of all the InstrumentName grobs on the current system. Once you start hiding staves, it's no longer guaranteed that the list of InstrumentNames will line up correctly with the list of VerticalAxisGroups.

Here's the code I'm working with now, modified from Harm's:

\version "2.18.2"

%% NB
%% It's a proof of concept, nothing more!!
%% Currently it will fail for various reasons with extended examples
#(define change-instr-names
  (lambda (grob)
    (let* ((sys (ly:grob-system grob))
           (sys-all-elts (ly:grob-object sys 'all-elements))
           (sys-all-elts-list
             (if (ly:grob-array? sys-all-elts)
                 (ly:grob-array->list sys-all-elts)
                 '()))
           ;; get all VerticalAxisGroup-grobs per System
           (vertical-axis-group-list
             (filter
               (lambda (g)
                 (grob::has-interface g 'hara-kiri-group-spanner-interface))
               sys-all-elts-list))

           ;; get all InstrumentName-grobs per System
           (instrument-names-list
             (filter
               (lambda (g)
                 (grob::has-interface g 'system-start-text-interface))
               sys-all-elts-list))
           
           ;; Make a list of pairs of VerticalAxisGroup and InstrumentName grobs
           ;; This assumes the correspondence will be correct,
           ;; but that is NOT guaranteed to be true
           (instr-axis-list
            (zip vertical-axis-group-list instrument-names-list))
           
           ;; Check if a VerticalAxisGroup is me or the next one below me
           ;; Keep if yes, ignore the rest
           (my-vindex (ly:grob-get-vertical-axis-group-index grob))
           (cousin-vaxis? (lambda (v)
                          (let ((vindex (ly:grob-get-vertical-axis-group-index v)))
                            (or (equal? my-vindex vindex)
                                (equal? (+ 1 my-vindex) vindex)))
                          ))
           
           (cousin-instr-list
            (filter
             (lambda (x)
               (cousin-vaxis? (car x)))
             instr-axis-list))
           
            ;; construct a nested list like
            ;;   ((#t #<Grob InstrumentName >)
            ;;    (#f #<Grob InstrumentName >))
            ;; the boolean indicates whether the Staff is alive, derived from
            ;; VerticalAxisGroup.Y-extent
            (alive?-instr-list
              (map
                (lambda (x)
                  (list (interval-sane? (ly:grob-property (car x) 'Y-extent)) (cadr x))
                  )
                cousin-instr-list))
           )

;            (display alive?-instr-list)
;            (display (map-in-order ly:grob-get-vertical-axis-group-index instrument-names-list))
;            (display (map-in-order (lambda (g) (ly:grob-property (cadr g) 'text)) instr-axis-list))
;            (display "\n\n")
           

        ;; First constructing a list and then fragmenting it in various ways is
        ;; not elegant, to say the least.
        ;; Just a proof of concept...
        (if (any not (map car alive?-instr-list))
            (let* ((instr-to-set
                     (filter car alive?-instr-list)
                     ))
              (for-each (lambda (inst) (ly:grob-set-property! (cadr inst) 'text
                (make-column-markup
                  (map
                    (lambda (arg)
                      (ly:grob-property arg 'text))
                    (map cadr alive?-instr-list)))))
                instr-to-set
                          )))
      )))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Example
%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\layout {
  \context {
    \Staff
    \RemoveEmptyStaves
  }
}
<<
  \new StaffGroup = "foo" <<
    \new Staff = "1" \with {
      \override VerticalAxisGroup.after-line-breaking = #change-instr-names
      instrumentName = "One" shortInstrumentName = "1"
    }
      { R1 \break c \break c }
    \new Staff = "2" \with {
      instrumentName = "Two" shortInstrumentName = "2"
    }
      { c1 c c }
  >>
  \new StaffGroup = "baz" <<
    \new Staff = "3" \with {
      \override VerticalAxisGroup.after-line-breaking = #change-instr-names
      instrumentName = "One" shortInstrumentName = "3" }
      { R1 \break c \break c }
    \new Staff = "4" \with { instrumentName = "Two" shortInstrumentName = "4" }
      { c1 R c }
  >>
>>
Reply | Threaded
Open this post in threaded view
|

Re: engraver to change staff name based on visibility of related staff?

Shevek
Sorry for the repeat messages. I now have a working version. This is a callback for InstrumentName.after-line-breaking. It looks at whether the next Staff below is alive on the current system, and if it's not, displays the instrumentName for this Staff instead of the shortInstrumentName. This is a bit simpler than automatically combining the names of hidden staves, but it serves more or less the same purpose. It might be a little nicer to define a new property for the combined/apart names, but I'm not sure how to do that.

\version "2.18.2"
\language "english"

\paper {
  left-margin = 1.5\cm
}

#(define change-instr-names
   (lambda (grob)
     (let* (
             ;; Get the VerticalAxisGroup grob belonging to me
             (my-vaxis-arr (ly:grob-object grob 'elements))
             (my-vaxis
              (car (if (ly:grob-array? my-vaxis-arr)
                       (ly:grob-array->list my-vaxis-arr)
                       '())))
           
             ;; Get all the VerticalAxisGroup grobs per system
             (sys (ly:grob-system grob))
             (sys-all-elts (ly:grob-object sys 'all-elements))
             (sys-all-elts-list
              (if (ly:grob-array? sys-all-elts)
                  (ly:grob-array->list sys-all-elts)
                  '()))
             (vertical-axis-group-list
              (filter
               (lambda (g)
                 (grob::has-interface g 'hara-kiri-group-spanner-interface))
               sys-all-elts-list))
           
             ;; Check if a VerticalAxisGroup is the next one below me
             (my-vindex (ly:grob-get-vertical-axis-group-index my-vaxis))
             (next-vaxis? (lambda (v)
                              (equal? (+ 1 my-vindex) (ly:grob-get-vertical-axis-group-index v)))
                              )
             ;; Get the next Staff below me
             (next-vaxis
              (car (filter next-vaxis? vertical-axis-group-list)))
             
             ;; Is it alive this system?
             (alive-next?
              (interval-sane? (ly:grob-property next-vaxis 'Y-extent)))
           
             )
       ; (display alive-next?)
       ; (display "\n\n")
       
       (if (not alive-next?)
           (ly:grob-set-property! grob 'text
             (ly:grob-property grob 'long-text))
           )
       )
     )
   )

%% Example starts here

global = {
  s1
  \break
  s1
  \break
  s1
}

fluteI = {
  c'''1
  \partcombineApart
  \oneVoice
  c'''1
  \partcombineChords
  c'''1
}

fluteII = {
  e''1
  \change Staff = "Fl2"
  \oneVoice
  e''1
  e''1
}

oboeI = {
  %  For parts that begin on separate staves, this is necessary:
  \once\set Staff.instrumentName = "1"
 
  \partcombineApart
  \oneVoice
  c'''1
  \partcombineChords
  c'''1
  \partcombineApart
  c'''1
}

oboeII = {
  \change Staff = "Ob2"
  \oneVoice
  e''1
  e''1
  e''1
}

\score {
  <<
    \new StaffGroup \with {
      instrumentName = "Flute  "
      shortInstrumentName = "Fl.  "
    } <<
      \new Staff = "Fl1" \with {
        instrumentName = \markup\column { "1" "2" }
        shortInstrumentName = "1"
        \override InstrumentName.after-line-breaking = #change-instr-names
      } << \global \partcombine \fluteI \fluteII >>
      \new Staff = "Fl2" \with {
        instrumentName = \markup\column { "2" }
        shortInstrumentName = \markup\column { "2" }
        \override VerticalAxisGroup.remove-empty = ##t
        \override VerticalAxisGroup.remove-first = ##t
       
      } << \global >>
    >>
    \new StaffGroup \with {
      instrumentName = "Oboe  "
      shortInstrumentName = "Ob.  "
    } <<
      \new Staff = "Ob1" \with {
        instrumentName = \markup\column { "1" "2" }
        shortInstrumentName = "1"
        \override InstrumentName.after-line-breaking = #change-instr-names
      } << \global \partcombine \oboeI \oboeII >>
      \new Staff = "Ob2" \with {
        instrumentName = \markup\column { "2" }
        shortInstrumentName = \markup\column { "2" }
        \override VerticalAxisGroup.remove-empty = ##t
        \override VerticalAxisGroup.remove-first = ##t
       
      } << \global >>
    >>
  >>
  \layout {
    \context {
      \Staff
      \override InstrumentName.self-alignment-X = #1
    }
  }
}

Reply | Threaded
Open this post in threaded view
|

Re: engraver to change staff name based on visibility of related staff?

Thomas Morley-2
2017-07-07 2:41 GMT+02:00 Shevek <[hidden email]>:
> Sorry for the repeat messages. [...]

Hi Saul,

no problem. I'm interested in the topic, though I've not the time to
look deeper into it yet. Maybe the upcoming weekend...

Cheers,
  Harm

_______________________________________________
lilypond-devel mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-devel