Spacing grobs with an invisible object between them (hack)

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

Spacing grobs with an invisible object between them (hack)

Paolo Prete-3
Hello,

I just had this idea about a *new* method of spacing.
Maybe this is nuts, and it's almost a hack, but I'm seeing that it could work, so, please, leave me a feedback.
The idea is create an invisible "obstacle", with a length set in staff-space units, between grobs.
Then, assign a position to each grob.

The obstacle has a grey color in the following example, but you can use white color as well.

Here is the example:

%%%%%%%%%%%%%%%%%%%%%%%%

#(define (SpaceBeforePos1 length)  #{ \tweak TextScript.outside-staff-priority 100 ^\markup{ \draw-line #(cons 0 length)} #})
#(define (SpaceBeforePos2 length)  #{ \tweak TextScript.outside-staff-priority 200 ^\markup{ \draw-line #(cons 0 length)} #})
#(define (SpaceBeforePos3 length)  #{ \tweak TextScript.outside-staff-priority 300 ^\markup{ \draw-line #(cons 0 length)} #})
#(define (SpaceBeforePos4 length)  #{ \tweak TextScript.outside-staff-priority 400 ^\markup{ \draw-line #(cons 0 length)} #})

#(define DynamicTextAtPos1 #{ \once \override DynamicLineSpanner.outside-staff-priority = 110 #})
#(define DynamicTextAtPos2 #{ \once \override DynamicLineSpanner.outside-staff-priority = 210 #})
#(define DynamicTextAtPos3 #{ \once \override DynamicLineSpanner.outside-staff-priority = 310 #})
#(define DynamicTextAtPos4 #{ \once \override DynamicLineSpanner.outside-staff-priority = 410 #})

#(define ScriptAtPos1 #{ \once \override Script.outside-staff-priority = 110 #})
#(define ScriptAtPos2 #{ \once \override Script.outside-staff-priority = 210 #})
#(define ScriptAtPos3 #{ \once \override Script.outside-staff-priority = 310 #})
#(define ScriptAtPos4 #{ \once \override Script.outside-staff-priority = 410 #})

#(define TupletBracketAtPos1 #{ \once \override TupletBracket.outside-staff-priority = 110 #})
#(define TupletBracketAtPos2 #{ \once \override TupletBracket.outside-staff-priority = 210 #})
#(define TupletBracketAtPos3 #{ \once \override TupletBracket.outside-staff-priority = 310 #})
#(define TupletBracketAtPos4 #{ \once \override TupletBracket.outside-staff-priority = 410 #})

#(define OttavaBracketAtPos1 #{ \once \override Staff.OttavaBracket.outside-staff-priority = 110 #})
#(define OttavaBracketAtPos2 #{ \once \override Staff.OttavaBracket.outside-staff-priority = 210 #})
#(define OttavaBracketAtPos3 #{ \once \override Staff.OttavaBracket.outside-staff-priority = 310 #})
#(define OttavaBracketAtPos4 #{ \once \override Staff.OttavaBracket.outside-staff-priority = 410 #})

{

\time 2/4

% assign a position to each grob

$TupletBracketAtPos1
$OttavaBracketAtPos2
$DynamicTextAtPos3
$ScriptAtPos4

% We want 4 staff spaces before position 1, 2 staff spaces before position 2, 3 staff-spaces before position 3 and 4 staff-spaces before position 4

\once \override TupletBracket.direction = #UP
\once \override TextScript.color = #grey
\tuplet 3/2 { \ottava #1 c'''-#(SpaceBeforePos1 4) -#(SpaceBeforePos2 2)  -#(SpaceBeforePos3 3) ^\mf -#(SpaceBeforePos4 4) ^>  c''' c''' \ottava #0 }

r2

% change the position of the grobs

$OttavaBracketAtPos1
$TupletBracketAtPos2
$ScriptAtPos3
$DynamicTextAtPos4

\once \override TupletBracket.direction = #UP
\once \override TextScript.color = #grey
\tuplet 3/2 { \ottava #1 c'''-#(SpaceBeforePos1 4) -#(SpaceBeforePos2 2)  -#(SpaceBeforePos3 3) ^\mf -#(SpaceBeforePos4 4) ^>  c''' c''' \ottava #0 }

}
Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Andrew Bernard
Hello Paolo,

What are you trying to achieve?

Andrew

On Thu, 6 Feb 2020 at 11:48, Paolo Prete <[hidden email]> wrote:

> I just had this idea about a *new* method of spacing.

Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Paolo Prete-3
Hi Andrew,

What I want to achieve  is to obtain a template for easy spacing objects vertically. This spacing is very hard to achieve when there are many layers of objects. And it becomes even harder when you realize that you have to change the order of the positions. Instead, with my system, I think it's possible to do it very easily. I just completed the template. Here is the new version. Please look at the result, and see how the spacing is made in the code (in a "graphic" way too).

(Lilybin preview)

(code)

%%%%%%%%%%%%%%%%%%%%%%%%

spacer =  #(define-music-function (dir pos length) (string? number? number?)
(let ()
  (begin
    (cond
 ((string=? dir "UP")
        #{ \tweak TextScript.color #grey \tweak TextScript.outside-staff-priority #(* 100 pos) ^\markup{ \translate #(cons 1 0) \draw-line #(cons 0 length)} #})
      (else
   #{ \tweak TextScript.color #grey \tweak TextScript.outside-staff-priority #(* 100 pos) _\markup{ \translate #(cons 1 0) \draw-line #(cons 0 length)} #})))))

setPosition = #(define-music-function (obj pos mus) (string? number? ly:music?)
(let ()
  (begin
    (cond
      ((string=? obj "TextScript")
        #{ \tweak TextScript.outside-staff-priority #(* 110 pos) #mus #})
 (else
        #{ \once \override #obj .outside-staff-priority = #(* 110 pos)  #})))))

{
\time 2/4
\set Staff.pedalSustainStyle = #'mixed
\once \override TupletBracket.direction = #UP

%
% Assign a position to each object
%
% Objects above staff
\setPosition "Script" 1 {}
\setPosition "TupletBracket" 2 {}
\setPosition "Slur" 3 {}
\setPosition "Staff.OttavaBracket" 4 {}
% Objects below staff
\setPosition "DynamicLineSpanner" 1 {}
\setPosition "Staff.SustainPedalLineSpanner" 2 {}
\setPosition "Staff.SostenutoPedalLineSpanner" 3 {}

\tuplet 3/2 { \ottava #1 c''''

-\spacer "UP" 5 4 -\setPosition "TextScript" 5 ^\markup { "Espressivo" }
-\spacer "UP" 4 2
-\spacer "UP" 3 3 ^(
-\spacer "UP" 2 2
-\spacer "UP" 1 0.7 ^>
%------------------------------------------$
%------------------------------------------$
%------------------------------------------$
%------------------------------------------$
%------------------------------------------$
-\spacer "DOWN" 1 2 \mf  
-\spacer "DOWN" 2 3 \sustainOn
-\spacer "DOWN" 3 4 \sostenutoOn
-\spacer "DOWN" 4 3 -\setPosition "TextScript" 4 _\markup { "Use pedal with care!"}

c'''' c'''')\sustainOff\sostenutoOff \ottava #0 }

}


On Thu, Feb 6, 2020 at 5:14 AM Andrew Bernard <[hidden email]> wrote:
Hello Paolo,

What are you trying to achieve?

Andrew

On Thu, 6 Feb 2020 at 11:48, Paolo Prete <[hidden email]> wrote:

> I just had this idea about a *new* method of spacing.
Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Aaron Hill
On 2020-02-06 6:10 am, Paolo Prete wrote:
> (code)

A few comments on the code:

1) "begin" is used to specify multiple expressions in a construct that
only accepts one.  "if" would be an example where "begin" could be
useful; but "let", like many other constructs, already accepts a list of
expressions to evaluate.

;;;;
(define (foo a)
   (let ((b (1+ a)))
     (begin
       (do-something a)
       (do-something b))))

;; ...would become...

(define (foo a)
   (let ((b (1+ a)))
     (do-something a)
     (do-something b)))
;;;;


2) "let" without any bindings would be superfluous syntax.

;;;;
(define (foo a)
   (let ()
     (do-something a)))

;; ...would become...

(define (foo a)
   (do-something a))
;;;;


3) "cond" is best used when there are more than two paths, otherwise
"if" is more concise.

;;;;
(define (foo a)
   (cond ((< 0 a) (do-something a))
         (else (do-another-thing a))))

;; ...would become...

(define (foo a)
   (if (< 0 a) (do-something a)
               (do-another-thing a)))
;;;;


4) ly:dir? and symbol-list? are built-in type predicates useful for
directions and grob paths, respectively.  These can replace your current
use of strings as inputs, which eliminates some potentially unsightly
quotation marks in the user code.


5) Event functions let the user specify direction with the normal syntax
(^, -, _), potentially obviating the need for a direction argument:

%%%%
\version "2.19.83"
foo = #(define-event-function (color) (color?)
   #{ -\tweak color #color -\markup "foo" #})
{ b'4 ^\foo #red b'4 _\foo #blue }
%%%%


-- Aaron Hill

Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Paolo Prete-3


On Thu, Feb 6, 2020 at 4:45 PM Aaron Hill <[hidden email]> wrote:
On 2020-02-06 6:10 am, Paolo Prete wrote:
> (code)

A few comments on the code:


Good, thanks

Here's the improved version without the noisy syntax and better names (OS = Outside Staff)
I ask you if is it possible to remove the noisy default  #OSSPW param as well, in the function call.

%%%%%%%%%%%%%%%%%%%%%%%%
\version "2.19.45"

#(define OSSPW 3)
#(define OSSPacerColor white)

OSSpacer =  #(define-music-function (pos height width) (number? number? (number? 2))
 #{ \tweak TextScript.color #OSSPacerColor \tweak TextScript.outside-staff-priority #(* 100 pos) -\markup{ \filled-box #'(0 . 3) #(cons 0 height) #0 } #})

setOSPosition = #(define-music-function (obj pos mus) (string? number? ly:music?)
  (if (string=? obj "TextScript")
    #{ \tweak TextScript.outside-staff-priority #(* 110 pos) #mus #}
    #{ \once \override #obj .outside-staff-priority = #(* 110 pos)  #}))

{
\time 2/4
\set Staff.pedalSustainStyle = #'mixed
\once \override TupletBracket.direction = #UP
#(set! OSSPacerColor grey)

%
% Assign a position to each object
%
% Objects above staff
\setOSPosition "Script" 1 {}
\setOSPosition "TupletBracket" 2 {}
\setOSPosition "Slur" 3 {}
\setOSPosition "Staff.OttavaBracket" 4 {}
% Objects below staff
\setOSPosition "DynamicLineSpanner" 1 {}
\setOSPosition "Staff.SustainPedalLineSpanner" 2 {}
\setOSPosition "Staff.SostenutoPedalLineSpanner" 3 {}

\tuplet 3/2 { \ottava #1 c''''

^\OSSpacer 5 4   #OSSPW -\setOSPosition "TextScript" 5 ^\markup { "Espressivo" }
^\OSSpacer 4 2   #OSSPW
^\OSSpacer 3 3   #OSSPW ^(
^\OSSpacer 2 2   #OSSPW
^\OSSpacer 1 0.7 #OSSPW ^>
%------------------------------------------$
%------------------------------------------$
%------------------STAFF-------------------$
%------------------------------------------$
%------------------------------------------$
_\OSSpacer 1 5 #OSSPW \mf  
_\OSSpacer 2 3 #OSSPW \sustainOn
_\OSSpacer 3 4 #OSSPW \sostenutoOn
_\OSSpacer 4 3 #OSSPW -\setOSPosition "TextScript" 4 _\markup { "Use pedal with care!"}

c'''' c'''')\sustainOff\sostenutoOff \ottava #0 }

}


Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Aaron Hill
On 2020-02-06 8:35 am, Paolo Prete wrote:
> I ask you if is it possible to remove the noisy default  #OSSPW param
> as
> well, in the function call.

Default arguments cannot be in the final position, which is why you have
to specify the value.

Consider whether you can reorder arguments or add an extra argument to
ensure a non-optional final parameter.

Another option would be to accept height and width in the same argument
using a custom type predicate:

%%%%
\version "2.19.83"

#(define (number-or-number-pair? x) (or (number? x) (number-pair? x)))

foo = #(define-scheme-function (arg) (number-or-number-pair?)
   (let ((width (if (pair? arg) (car arg) 0))
         (height (if (pair? arg) (cdr arg) arg)))
     #{ \markup #(format #f "width=~a, height=~a" width height) #}))

\foo 1
\foo #'(2 . 3)
%%%%


-- Aaron Hill

Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Paolo Prete-3


On Thu, Feb 6, 2020 at 6:04 PM Aaron Hill <[hidden email]> wrote:

Another option would be to accept height and width in the same argument
using a custom type predicate:


Thanks again. Here's the _complete_ version.


I find it *very* useful. In this way you can easily add a *precise* space between grobs, position grobs vertically in a chosen order and avoid collisions without 
troubling with unexpected behaviors of the avoid-collision properties.
I still ask to Lilypond people a feedback for this and if is there an easier alternative.

HTH
P

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\version "2.19.45"

#(define OSSPW 3)
#(define OSSPacerColor white)

#(define (number-or-number-pair? x) (or (number? x) (number-pair? x)))

OSSpacer =  #(define-music-function (pos arg) (number? number-or-number-pair?)
(let ((width (if (pair? arg) (cdr arg) OSSPW))
      (height (if (pair? arg) (car arg) arg)))
 #{ \tweak TextScript.color #OSSPacerColor \tweak TextScript.outside-staff-priority #(* 100 pos) -\markup{ \filled-box #(cons 0 width) #(cons 0 height) #0 } #}))

setOSPosition = #(define-music-function (obj pos mus) (string? number? ly:music?)
  (if (string=? obj "TextScript")
    #{ \tweak TextScript.outside-staff-priority #(* 105 pos) #mus #}
    #{ \once \override #obj .outside-staff-priority = #(* 105 pos)  #}))

{

\time 2/4
\set Staff.pedalSustainStyle = #'mixed
\once \override TupletBracket.direction = #UP
#(set! OSSPacerColor grey)

% Assign a position to each object.
% A lower position is nearer to the staff.
% Different markups can have different positions
% and their positions is set "inline" (see below)

% Objects above staff
\setOSPosition "Script" 1 {}
\setOSPosition "TupletBracket" 2 {}
\setOSPosition "Slur" 3 {}
\setOSPosition "Staff.OttavaBracket" 4 {}
% Objects below staff
\setOSPosition "DynamicLineSpanner" 1 {}
\setOSPosition "Staff.SustainPedalLineSpanner" 2 {}
\setOSPosition "Staff.SostenutoPedalLineSpanner" 3 {}

\tuplet 3/2 { \ottava #1 c''''

% Place objects above/below the staff according to
% the previous positions list and create barriers between them.
% Each barrier has a default width = $OSSPW (3)

^\OSSpacer 5 4 -\setOSPosition "TextScript" 5 ^\markup { "Espressivo" }
^\OSSpacer 4 2
^\OSSpacer 3 3 ^(
^\OSSpacer 2 2
^\OSSpacer 1 0.7 ^>
%------------------------------------------
%------------------------------------------
%------------------STAFF-------------------
%------------------------------------------
%------------------------------------------
_\OSSpacer 1 #'(5 . 8) \mf  
_\OSSpacer 2 3 \sustainOn
_\OSSpacer 3 4 \sostenutoOn
_\OSSpacer 4 3 -\setOSPosition "TextScript" 4 _\markup { "Use pedal with care!"}

c'''' c'''')\sustainOff\sostenutoOff \ottava #0 }

}

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

Re: Spacing grobs with an invisible object between them (hack)

Paolo Prete-3
In reply to this post by Aaron Hill


On Thu, Feb 6, 2020 at 6:04 PM Aaron Hill <[hidden email]> wrote:
On 2020-02-06 8:35 am, Paolo Prete wrote:
> I ask you if is it possible to remove the noisy default  #OSSPW param
> as
> well, in the function call.


Also: I'm seeing that it compiles well on 2.19.45 (lilybin) but doesn't compile on 2.19.83 

\setOSPosition "Staff.SustainPedalLineSpanner" 2 {}
test.ly:18:18: error: bad grob property path
\once \override  
                $obj .outside-staff-priority = #(* 105 pos)  


How can I fix it? Thanks,
Paolo



 
Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Aaron Hill
On 2020-02-06 3:02 pm, Paolo Prete wrote:

> On Thu, Feb 6, 2020 at 6:04 PM Aaron Hill <[hidden email]>
> wrote:
>
>> On 2020-02-06 8:35 am, Paolo Prete wrote:
>> > I ask you if is it possible to remove the noisy default  #OSSPW param
>> > as
>> > well, in the function call.
>>
>>
> Also: I'm seeing that it compiles well on 2.19.45 (lilybin) but doesn't
> compile on 2.19.83
>
> \setOSPosition "Staff.SustainPedalLineSpanner" 2 {}
> test.ly:18:18: error: bad grob property path
> \once \override
>                 $obj .outside-staff-priority = #(* 105 pos)

This is where symbol-list? comes in.  LilyPond appears to have handled
strings and symbols in similar ways in the past such that you can
interchange them.  Something must have changed between the version, but
the solution should be to avoid strings and stick only to symbols.

Consider the following examples:

%%%%
\version "2.19"

foo = #(define-music-function (grobpath) (symbol-list?)
   #{ \once \override #grobpath . color = #red #})

{ \foo Staff.Clef \foo Slur aes'4( b' \foo Accidental cis''2) }

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

baz = #(define-music-function (arg) (symbol-list-or-music?)
   (if (ly:music? arg)
       #{ -\tweak color #green #arg #}
       #{ \once \override #arg . color = #red #}))

{ \baz Staff.Clef aes'4 -\baz ( b' \baz Accidental cis''2) }
%%%%

This second pattern is of more importance.  It mimics the behavior and
usage of \offset which can be used in both \override-style and
\tweak-style.

I have applied this logic to your code and made some changes [1].

[1]: http://lilybin.com/bt0t2d/11

* OSSpacer did not need to specify TextScript when \tweaking.
* setOSPosition uses the pattern above to accept either a grob path or
music.
* Argument order changes so that "pos" is first,
   but the spurious empty music {} expressions are gone.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\version "2.19.45"

#(define OSSPW 3)
#(define OSSPacerColor white)

#(define (number-or-number-pair? x) (or (number? x) (number-pair? x)))

OSSpacer =  #(define-music-function (pos arg) (number?
number-or-number-pair?)
(let ((width (if (pair? arg) (cdr arg) OSSPW))
       (height (if (pair? arg) (car arg) arg)))
  #{ -\tweak color #OSSPacerColor -\tweak outside-staff-priority #(* 100
pos)
     -\markup{ \filled-box #(cons 0 width) #(cons 0 height) #0 } #}))

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

setOSPosition = #(define-music-function (pos arg) (number?
symbol-list-or-music?)
   (if (ly:music? arg)
     #{ -\tweak outside-staff-priority #(* 105 pos) #arg #}
     #{ \once \override #arg .outside-staff-priority = #(* 105 pos) #}))

{

\time 2/4
\set Staff.pedalSustainStyle = #'mixed
\once \override TupletBracket.direction = #UP
#(set! OSSPacerColor grey)

% Assign a position to each object.
% A lower position is nearer to the staff.
% Different markups can have different positions
% and their positions is set "inline" (see below)

% Objects above staff
\setOSPosition 1 Script
\setOSPosition 2 TupletBracket
\setOSPosition 3 Slur
\setOSPosition 4 Staff.OttavaBracket
% Objects below staff
\setOSPosition 1 DynamicLineSpanner
\setOSPosition 2 Staff.SustainPedalLineSpanner
\setOSPosition 3 Staff.SostenutoPedalLineSpanner

\tuplet 3/2 { \ottava #1 c''''

% Place objects above/below the staff according to
% the previous positions list and create barriers between them.
% Each barrier has a default width = $OSSPW (3)

^\OSSpacer 5 4 -\setOSPosition 5 ^\markup { "Espressivo" }
^\OSSpacer 4 2
^\OSSpacer 3 3 ^(
^\OSSpacer 2 2
^\OSSpacer 1 0.7 ^>
%------------------------------------------
%------------------------------------------
%------------------STAFF-------------------
%------------------------------------------
%------------------------------------------
_\OSSpacer 1 #'(5 . 8) \mf
_\OSSpacer 2 3 \sustainOn
_\OSSpacer 3 4 \sostenutoOn
_\OSSpacer 4 3 -\setOSPosition 4 _\markup { "Use pedal with care!"}

c'''' c'''')\sustainOff\sostenutoOff \ottava #0 }

}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


-- Aaron Hill

Reply | Threaded
Open this post in threaded view
|

Re: Spacing grobs with an invisible object between them (hack)

Paolo Prete-3


On Fri, Feb 7, 2020 at 4:28 AM Aaron Hill <[hidden email]> wrote:



I have applied this logic to your code and made some changes [1].

[1]: http://lilybin.com/bt0t2d/11


That's great, Aaron!

Now we have a very robust method for *easy* spacing *complex* vertical layouts.
I would not even consider this a "hack", because placing a barrier between objects is something logical and natural
in many situations. And giving a position to the objects is something logical and natural too, for complex layouts.
Now, it would be great if this fully works for SVG too. 
As you can see, the OttavaBracket creates collisions in the SVG output when an outside-staff-priority is assigned.
Then, this spacing method works fine for PDF output, but has a bug for the SVG one.
A request has already be sent to the bug ML, but I suspect that the development of the SVG layer is not active anymore.


Honestly, I don't know where to start investigating in the Lilypond src (framework-svg.scm?  output-svg.scm?)

Thanks again for your patience!