An alternative outside-staff spacing method (new version)

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

An alternative outside-staff spacing method (new version)

Paolo Prete-3
Hello all,

thanks to the _great_ support of Aaron and Harm, I implemented this new version of the outside-staff spacing method I proposed some days ago.
Please note that in this way you can order and pad outside staff objects in *any* combination you want, in a very easy way. And you can obtain complex layouts of objects.
You can easily switch the positions, you can add padding empty boxes and you can pad boxes around grobs in any direction (so to bypass the limitation of the outside-staff-padding property that bottom and top padding must be the same) 

TODO:

1) I could not find a way to add a box around piano pedals (and ask to the gurus which  ly:xxx::print interface should I use). Obviously, you can easily compensate this by adding two padding boxes above and below pedals, as the following snippet shows. 

2) Note too that you don't have to specify any position for the "inline" grobs (-\tweak method). But I had to specify this position for TupletBracket as well as for Staff.OttavaBracket. I ask the Scheme gurus if is there a way to put them inline as well.

3) Note too that the Scheme code is somewhat redundant. Any suggestion from the Scheme gurus, for cleaning it, will be obviously greatly accepted.

HTH!
P

%%%%%%%%%% SCHEME CODE %%%%%%%%%%

\version "2.19.83"
#(define PADDER_RECT_DEF_W (cons -3 3))
#(define OSPadderColor grey)
#(define OSPadderThickness 0.1)
#(define posIdxUp 0)
#(define posListUp '())
#(define posIdxDown 0)
#(define posListDown '())
#(define OSBox #{ \markup " " #})

#(define (box-stencil stencil thickness padding color expand?)
   "Add a box around @var{stencil}, producing a new stencil."
   (define (css-style-padding padding)
     ;; padding => (top right bottom left)
     (cond
       ((or (null? padding) (eqv? #f padding)) '(0 0 0 0))
       ((number? padding) (make-list 4 padding))
       ((number-pair? padding)
         (list (car padding) (cdr padding)
               (car padding) (cdr padding)))
       ((and (number-list? padding) (<= (length padding) 4))
         (case (length padding)
           ((1) (make-list 4 (first padding)))
           ((2) (list (first padding) (second padding)
                      (first padding) (second padding)))
           ((3) (list (first padding) (second padding)
                      (third padding) (second padding)))
           (else padding)))
       (else
         (begin (ly:warning "Ignoring invalid padding: ~a" padding)
                '(0 0 0 0)))))
          (let* ((padding (css-style-padding padding))
                 (padding-top (first padding))
                 (padding-right (second padding))
                 (padding-bottom (third padding))
                 (padding-left (fourth padding))

          (x-ext-orig (ly:stencil-extent stencil X))
          (y-ext-orig (ly:stencil-extent stencil Y))
          (x-ext-inner
            (cons (- (interval-start x-ext-orig) padding-left)
                  (+ (interval-end x-ext-orig) padding-right)))
          (y-ext-inner
            (cons (- (interval-start y-ext-orig) padding-bottom)
                  (+ (interval-end y-ext-orig) padding-top)))
          (x-ext-outer (interval-widen x-ext-inner thickness))
          (y-ext-outer (interval-widen y-ext-inner thickness))
          (x-ext-new (if expand? x-ext-outer x-ext-orig))
          (y-ext-new (if expand? y-ext-outer y-ext-orig))

          (x-rule (make-filled-box-stencil (cons 0 thickness) y-ext-inner))
          (y-rule (make-filled-box-stencil x-ext-outer (cons 0 thickness)))
          (box (stencil-with-color
            (ly:stencil-add
              (ly:stencil-translate-axis y-rule (interval-end y-ext-inner) Y)
              (ly:stencil-translate-axis x-rule (interval-end x-ext-inner) X)
              (ly:stencil-translate-axis y-rule (interval-start y-ext-outer) Y)
              (ly:stencil-translate-axis x-rule (interval-start x-ext-outer) X))
            color)))
     (ly:make-stencil
       (ly:stencil-expr (ly:stencil-add stencil box))
       x-ext-new y-ext-new)))

#(define* (make-stencil-boxer thickness padding callback
            #:optional (color OSPadderColor) (expand? #t))
   "Return function that adds a box around the grob passed as argument."
   (lambda (grob)
     (box-stencil (callback grob) thickness padding color expand?)))

#(define (member? x list)
  (cond
    ((null? list) #f)
      ((eqv? x (car list)))
    (else (member? x (cdr list)))))

#(define (next-not-in-list n list)
  (let ((s (+ 1 n)))
    (if (member? s list)
      (next-not-in-list s list) s)))

resetOSPositions  =  #(define-scheme-function () ()
  (set! posIdxUp 0)
  (set! posListUp '())
  (set! posIdxDown 0)
  (set! posListDown '()))

#(define (symbol-list-or-music? x) (or (symbol-list? x) (ly:music? x)))

OSOObj = #(define-music-function (arg) (ly:music?)
  (let ((posIdx 0)
        (name (ly:music-property arg 'name)))
  (if (eq? (ly:music-property arg 'direction) UP)
    (begin
      (set! posIdxUp (next-not-in-list posIdxUp posListUp))
      (set! posListUp (cons posIdxUp posListUp))
      (set! posListUp (sort! posListUp <))
      (set! posIdx posIdxUp))
    (begin
      (set! posIdxDown (next-not-in-list posIdxDown posListDown))
      (set! posListDown (cons posIdxDown posListDown))
      (set! posListDown (sort! posListUp <))
      (set! posIdx posIdxDown)))
    (cond
      ((eq? name 'AbsoluteDynamicEvent)
        #{ -\tweak DynamicLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})    
      ((eq? name 'SustainEvent)
        #{ -\tweak SustainPedalLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})
      ((eq? name 'SostenutoEvent)
        #{ -\tweak SostenutoPedalLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})
      ((eq? name 'UnaCordaEvent)
        #{ -\tweak UnaCordaPedalLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})      
      (else
        #{ -\tweak outside-staff-priority #(* 100 posIdx) #arg #}))))

OSOPadder = #(define-music-function (color thickness padding arg) ((color? white) (number? OSPadderThickness) scheme? symbol-list-or-music?)
  (let ((posIdx 0)
        (name (if (ly:music? arg) (ly:music-property arg 'name) "")))
    (if (ly:music? arg)
      (if (eq? (ly:music-property arg 'direction) UP)
        (begin
          (set! posIdxUp (next-not-in-list posIdxUp posListUp))
          (set! posListUp (cons posIdxUp posListUp))
          (set! posListUp (sort! posListUp <))
          (set! posIdx posIdxUp))
        (begin
          (set! posIdxDown (next-not-in-list posIdxDown posListDown))
          (set! posListDown (cons posIdxDown posListDown))
          (set! posListDown (sort! posListUp <))
          (set! posIdx posIdxDown))))
    (if (ly:music? arg)
      (cond
        ((eq? name 'AbsoluteDynamicEvent) #{
          -\tweak DynamicText.stencil #(make-stencil-boxer thickness padding ly:text-interface::print color)
          -\tweak DynamicLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})    
        ((eq? name 'SustainEvent) #{
          -\tweak SustainPedalLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})
        ((eq? name 'SostenutoEvent) #{
          -\tweak SostenutoPedalLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})
        ((eq? name 'TextScriptEvent) #{
          -\tweak TextScript.stencil #(make-stencil-boxer thickness padding ly:text-interface::print color)
          -\tweak TextScript.outside-staff-priority #(* 100 posIdx) #arg #})      
        ((eq? name 'ArticulationEvent) #{
          -\tweak Script.stencil #(make-stencil-boxer thickness padding ly:script-interface::print color)
          -\tweak outside-staff-priority #(* 100 posIdx) #arg #})
        ((eq? name 'UnaCordaEvent) #{
          -\tweak UnaCordaPedalLineSpanner.outside-staff-priority #(* 100 posIdx) #arg #})      
        (else #{
          -\tweak outside-staff-priority #(* 100 posIdx) #arg #}))
      ;else (symbols)
      (cond
        ((memv 'OttavaBracket arg) #{
           \once \override Staff.OttavaBracket.stencil =
           #(make-stencil-boxer OSPadderThickness padding ly:ottava-bracket::print color)  #})
        ((memv 'TupletBracket arg) #{
           \once \override TupletBracket.stencil =
           #(make-stencil-boxer OSPadderThickness padding ly:tuplet-bracket::print color)  #})))))  

setOSPosition = #(define-music-function (direction pos arg) (number? number? symbol-list?)                                      
  (if (eq? direction UP)
    (begin
      (set! posListUp (cons pos posListUp))
      (set! posListUp (sort! posListUp <)))
    (begin
      (set! posListDown (cons pos posListDown))
      (set! posListDown (sort! posListDown <))))
    #{ \once \override #arg .outside-staff-priority = #(* 100 pos) #})

%%%%%%%%%% USER CODE %%%%%%%%%%

#(define OSOPCOL grey)

{

\time 2/4

\set Staff.pedalSustainStyle = #'mixed
\once \override TupletBracket.direction = #UP
\override Staff.OttavaBracket.outside-staff-padding = 0
\override Staff.TextScript.outside-staff-padding = 0
\override Staff.Script.outside-staff-padding = 0
\override TupletBracket.outside-staff-padding = 0
\override DynamicLineSpanner.outside-staff-padding = 0

#(define OSSLayoutA  #{

\resetOSPositions
\setOSPosition #UP 2 TupletBracket
\setOSPosition #UP 6 Staff.OttavaBracket
\OSOPadder #OSOPCOL #'(0 1 0 1) Staff.OttavaBracket
\OSOPadder #OSOPCOL #'(0 1 0 1) TupletBracket

#})

#(define OSSLayoutB  #{

%-----------ABOVE STAFF------------ (bottom -> top order)
\OSOPadder #OSOPCOL #'(3 1 1 1) ^\OSBox
\OSOPadder #OSOPCOL #'(1 1 1 1) ^\OSBox
\OSOObj                         ^(
\OSOPadder #OSOPCOL #'(1 1 1 1) ^\OSBox
\OSOPadder #OSOPCOL #'(1 1 1 1) ^\OSBox
\OSOPadder #OSOPCOL #'(0 1 0 1) ^>
\OSOPadder #OSOPCOL #'(1 1 1 1) ^\OSBox

%-----------BELOW STAFF------------ (top -> bottom order)
\OSOPadder #OSOPCOL #'(0 1 3 1) _\mf
\OSOPadder #OSOPCOL #'(1 1 1 1) _\OSBox
\OSOObj                         _\sostenutoOn
\OSOPadder #OSOPCOL #'(1 1 1 1) _\OSBox
\OSOObj                         _\sustainOn
\OSOPadder #OSOPCOL #'(1 1 1 1) _\OSBox
\OSOPadder #OSOPCOL #'(0 1 0 1) _\markup {"Use ped. with care!"}
\OSOPadder #OSOPCOL #'(1 1 1 1) _\OSBox

#})

\tuplet 3/2 { $OSSLayoutA \ottava #1 c'''' $OSSLayoutB a'''' c'''')\sustainOff\sostenutoOff \ottava #0 }

}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Reply | Threaded
Open this post in threaded view
|

Re: An alternative outside-staff spacing method (new version)

Aaron Hill
On 2020-02-11 8:05 am, Paolo Prete wrote:
> thanks to the _great_ support of Aaron and Harm, I implemented this new
> version of the outside-staff spacing method I proposed some days ago.

I am getting quite a few errors and warnings compiling the code pasted
in the email.  I wonder if the email process has mangled something.  It
might be worth attaching the original source, so that formatting can be
preserved.


================================================================
> 1) I could not find a way to add a box around piano pedals (and ask to
> the gurus which  ly:xxx::print interface should I use). Obviously, you
> can
> easily compensate this by adding two padding boxes above and below
> pedals,
> as the following snippet shows.

The Internals Reference lists ly:piano-pedal-bracket::print for the
PianoPedalBracket, ly:sustain-pedal::print for the SustainPedal and
ly:text-interface::print for both SostenutoPedal and UnaCordaPedal.

But before going down that path, are you committed to supporting only
2.19 and newer?  grob-transformer can make it easier to override the
stencil for a grob without needing to know what the original procedure
is:

%%%%
#(define* (make-stencil-boxer thickness padding
            #:optional (callback #f) (color red) (expand? #t))
   "Return function that adds a box around the grob passed as argument."
   (if (procedure? callback)
     (lambda (grob)
       (box-stencil (callback grob) thickness padding color expand?))
     (grob-transformer 'stencil (lambda (grob orig)
       (box-stencil orig thickness padding color expand?)))))
%%%%

The above should be a drop-in replacement for the existing definition.  
It makes callback optional, defaulting to using grob-transformer.  But
if one does need to specify a procedure, they can get the original
behavior.

Then you should only need to use...

%%%%
   -\tweak stencil #(make-stencil-boxer thickness padding #f color)
%%%%

...for anything.  That might clean up a little bit of logic.


================================================================
> 3) Note too that the Scheme code is somewhat redundant. Any suggestion
> from
> the Scheme gurus, for cleaning it, will be obviously greatly accepted.

member? should not be needed.  Scheme already provides memq, memv, and
member where the difference is which form of equality is used--eq?, eqv?
and equal?, respectively.

There appears to be a typo where your logic is sorting the "up" list
where it should be sorting the "down" list.  This exists in both OSOObj
and OSOPadder.  Even with the correction, the duplication of logic and
the number of globals to maintain these lists suggests it would be
better to encapsulate this logic.

Assuming I understood your intention, here is one way to do it:

%%%%
\version "2.19.83"

#(define (make-indexer)
   (define ((less y) x) (< x y))
   (let ((indices '()) (prev 0))
     (lambda* (#:optional (index (1+ prev)) (keep-prev? #f) )
       (let loop ((lst (drop-while (less index) indices)) (idx index))
         (if (or (null? lst) (< idx (car lst)))
           (call-with-values
             (lambda () (span (less idx) indices))
             (lambda (before after)
               (set! indices (append before (list idx) after))
               (or keep-prev? (set! prev idx)) idx))
           (loop (cdr lst) (1+ idx)))))))

#(define foo (make-indexer))
#(define baz (make-indexer))

#(format #t "\n (foo)      : expecting 1, got ~a" (foo))
#(format #t "\n (foo 3)    : expecting 3, got ~a" (foo 3))
#(format #t "\n (foo 1)    : expecting 2, got ~a" (foo 1))
#(format #t "\n (foo)      : expecting 4, got ~a" (foo))

#(format #t "\n (baz 2)    : expecting 2, got ~a" (baz 2))
#(format #t "\n (baz 5 #t) : expecting 5, got ~a" (baz 5 #t))
#(format #t "\n (baz)      : expecting 3, got ~a" (baz))
%%%%

Invoking make-indexer will return a procedure for generating unique
indices on demand.  Each instance tracks its own returned indices to
avoid duplicates.  The generator function follows your original logic to
return the numerically next unused index based on the most recently
returned value.  The first optional argument is a number requesting a
specific index; though, if already used, the next available index is
returned.  The second optional argument is a boolean indicating the
generator should preserve the prior recently returned value.


-- Aaron Hill

Reply | Threaded
Open this post in threaded view
|

Re: An alternative outside-staff spacing method (new version)

Paolo Prete-3


On Tue, Feb 11, 2020 at 9:15 PM Aaron Hill <[hidden email]> wrote:
On 2020-02-11 8:05 am, Paolo Prete wrote:
> thanks to the _great_ support of Aaron and Harm, I implemented this new
> version of the outside-staff spacing method I proposed some days ago.

I am getting quite a few errors and warnings compiling the code pasted
in the email.  I wonder if the email process has mangled something.  It
might be worth attaching the original source, so that formatting can be
preserved.


Here's an attachment of a new version, with your last suggestions.
I left unchanged the sorting method (I have to investigate better what you said), but I wonder, meanwhile, if
is it possible to add a hairpin to the dynamic below the staff ( mf <  ff ) with all the positions preserved.

Thanks!
P


outsideStaffObjs.ly (13K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: An alternative outside-staff spacing method (new version)

Paolo Prete-3
I just fixed the typo highlighted by Aaron. It was responsible of the hairpin problem. 
I also added these two lines, which strengthen the snippet in case of slurs and hairpins 

\override Staff.TextScript.avoid-slur = #'ignore
\override Staff.DynamicLineSpanner.outside-staff-padding = 0

Now all seems to work. Attached to this post there's the updated version.

HTH
P


outsideStaffObjs.ly (13K) Download Attachment