lilypond-user
[Top][All Lists]

 From: Lukas-Fabian Moser Subject: Re: Questions about HorizontalBracket Date: Tue, 15 Sep 2020 13:04:01 +0200 User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.10.0

```New version with some typos removed:

\version "2.21.0"

% -------------- Scheme functions for dealing with 2D vectors -------------
% A vector is represented as a pair v = (x . y), hence
% x is (car v) and y is (cdr v).

% Some routines for calculating with 2D vectors (given as scheme pairs)
#(define (vector-add v w) ; Calculate v+w
(cons (+ (car v) (car w))
(+ (cdr v) (cdr w))))
% Remember that (cons a b) by definition constructs the pair (a . b).

#(define (vector-substract v w) ; Calculate v-w
(cons (- (car v) (car w))
(- (cdr v) (cdr w))))

#(define (vector-stretch factor v) ; Calculate factor * v.
; (The mathematician in me wanted to write lambda * v, but lambda
; is a sacred reserved word in scheme :-).)
(cons (* (car v) factor)
(* (cdr v) factor)))

#(define (scalar-product v w) ; Euclidean scalar product of vectors
(+ (* (car v) (car w))
(* (cdr v) (cdr w))))

#(define (midpoint v w) ; calculate (v+w)/2

#(define (rotate-90-ccw v)
; rotate vector 90° counter-clockwise
; (mathematically positive direction)
(cons (- (cdr v)) (car v)))

#(define (normal p q)
; constructs a normal vector to the line from p to q.
; the length of the normal vector will be proportional to
; the distance [pq].
(rotate-90-ccw (vector-substract q p)))

#(define (on-which-halfplane v normal start)
```
; A line through "start" with fixed normal vector "normal" cuts the plane
```   ; into two half-planes. This function returns
; 0 if v lies on the line itself,
; +1 if v lies in the half plane that the normal vector points to,
; -1 otherwise.
(let ((dist (- (scalar-product normal v)
(scalar-product normal start))))
(cond ((> dist 0) 1) ; is there no "sgn" function in guile?!
((< dist 0) -1)
(else 0))
))

% -------------- Markup functions replacing slur shapes -------------
% The following markup functions expect a list of control points
% like they are used for slurs:
% (start 1st-directional-point 2nd-directional-point stop)

% First, we define shortcuts for easy construction of \path's.
% [ Background: We want to construct a markup using \path. As you can
%   see in http://lilypond.org/doc/v2.20/Documentation/notation/graphic,
%   \path expects a list of commands of the form
%     '((moveto 0 0)
%       (lineto -1 1)
%       (lineto 1 1)
%       (lineto 1 -1)
%       (curveto -5 -5 -5 5 -1 0)
%       (closepath)),
%   i.e. each element is itself a list, starting with a command symbol like
%   'lineto followed by numeric parameters.
%   But for us, points/vectors are _pairs_ of numbers.
```
%   So, we define a "moveto" command does the necessary conversion: It combines
```%   - the symbol "moveto",
%   - the x-coordinate of point p (i.e. (car p))
%   - the y coordinate of point p (i.e. (cdr p))
%   into a list. ]
#(define (moveto p) (list 'moveto (car p) (cdr p)))
#(define (lineto p) (list 'lineto (car p) (cdr p)))

% Markup functions are defined by #(define-markup-command ...), see
```
% http://lilypond.org/doc/v2.20/Documentation/extending/new-markup-command-definition
```%
% [ Note that the syntax for defining new markup commands differs from
%   the syntax for defining new music functions:
```
% (http://lilypond.org/doc/v2.20/Documentation/extending/music-function-definitions)
```%
```
%   #(define-markup-command (name-of-new-command layout props arguments...) (type-predictes...) ...)
```%   vs.
```
%   name-of-new-music-function = #(define-music-function (arguments...) (type-predicates...) ...)
```%
%   One of the reasons is that #(define-markup-command) defines not only
%   \name-of-new-command but also make-(name-of-new-command)-markup
%   to be used in scheme; see below. ]

#(define-markup-command
(slurReplacementVShape layout props control-points)
```
(number-pair-list?) ; The only actual parameter (control-points) should be a list of (we expect: four) number pairs
```  (let* ((start (first control-points))
(1st-directional-point (second control-points))
(2nd-directional-point (third control-points))
(stop (fourth control-points)))
(interpret-markup layout props #{
\markup {
\path #0.1 #(list (moveto start)
; from start ...
```
(lineto (midpoint 1st-directional-point 2nd-directional-point))                           ; ... draw line to the midpoint of the two directional control points  ...
```                          ; ... and then to stop.
(lineto stop))
} #})))

#(define-markup-command
(slurReplacementBracket layout props control-points)
(number-pair-list?)
(let* ((start (first control-points))
(stop (fourth control-points))
(1st-directional-point (second control-points))
; We have to find out the direction in which the bracket should
; protrude. The idea here is to use the first directional
; control point of the slur and draw the bracket such that
; it lies in the half-plane (with respect to the line through
; "start" and "stop" in which that control point lies.)
(normal (normal start stop))
(scaled-normal
(vector-stretch
```
(* 0.075 (on-which-halfplane 1st-directional-point normal start))
```           normal)))
(interpret-markup layout props #{
\markup {
\path #0.1
#(list (moveto start)
; draw lines:
; from start to start + scaled-normal
;            to stop + scaled-normal
;            to stop
; yielding a bracket-shaped path.
(lineto stop))
} #})))

% Might as well test the markup functions right away:

\markup {
Test of markup command "\slurReplacementVShape:"
\slurReplacementVShape #'((0 . 0) (2 . 2) (5 . 3) (8 . 1))
}

\markup {
Test of markup command "\slurReplacementBracket:"
\slurReplacementBracket #'((0 . 0) (2 . 2) (5 . 3) (8 . 1))
}

% -------------- Macros for tweaking slurs -------------

VShapeSlur = \tweak stencil
#(lambda (grob)
(grob-interpret-markup
; Interpret as a markup (see bonus consideration below) ...
```
grob ; ... using the layout/properties settings active for the current grob ...
```    ; ... the following markup:
(make-slurReplacementVShape-markup
; What you would write inside a LilyPond \markup as
; \slurReplacementVShape {parameters},
; in scheme becomes:
; (make-slurReplacementVShape-markup parameters)
;
; As parameters, we take the control-points property of the
; current grob (which we know will be a slur, because that's where
; we intend to use the \VShapeSlur command).
; The fact that slurs do have a property 'control-points can be
; found in
; http://lilypond.org/doc/v2.20/Documentation/internals/slur
(ly:grob-property grob 'control-points))))
\etc

bracketSlur = \tweak stencil
#(lambda (grob)
(grob-interpret-markup
grob (make-slurReplacementBracket-markup
(ly:grob-property grob 'control-points))))
\etc

% -------------- Tests -------------

\relative c' {
c1 \bracketSlur ( d) e \bracketSlur( f g a g f')
c,1 \VShapeSlur ( d) e \VShapeSlur( f g a g f')
}

\markup {
```
The "\=..." mechanism for distinguishing arbitrarily many (maybe overlapping)
```  slurs also can be used:
}
\relative c' {
```
c4 \VShapeSlur \=0_( \VShapeSlur \=1^( d e \bracketSlur \=2_( f\=1) e^( d\=2) c b\=0))
```}

\markup \wordwrap {
Nowhere in our definition of "\\VShapeSlur" do we actually require the
```
"\\tweak"ed grob to be a slur. Everything that has suitable 'control-points
```  should work - for example, a tie! And behold:
}
\relative c' {
c1 \VShapeSlur ~ c
d'1 \bracketSlur ~ d
}
\markup \wordwrap {
```
(Turns out that naming our tweak macros "\\VShapeSlur" and "\\bracketSlur"
```  might have been a little bit rash.)
}

```
% ----- Bonus consideration ("what I learned while writing this") ----------------
```% What's the deal with
% (interpret-markup layout props ...) vs. (grob-interpret-markup grob ...) ?
%
% Both turn a markup into a stencil ("they draw the markup"). For this,
```
% they need context: For instance, settings about the current font-size etc.,
```% which are stored in the layout and props variables. So the question
% becomes: What does grob-interpret-markup do?
% To find out, I simply looked up its definition in LilyPond itself. In
% scm/output-lib.scm, we find:
%
%   (define-public (grob-interpret-markup grob text)
%     (let* ((layout (ly:grob-layout grob))
%            (defs (ly:output-def-lookup layout 'text-font-defaults))
%            (props (ly:grob-alist-chain grob defs)))
%
%       (ly:text-interface::interpret-markup layout props text)))
%
% To wit, grob-interpret-markup asks the grob to reveal the suitable
```
% layout and props that are needed to call ly:text-interface::interpret-markup.
```%
% BUT: What about the prefix "ly:text-interface::"?
% The answer may be found in scm/markup.scm:
%
%   (define-public interpret-markup ly:text-interface::interpret-markup)
%
% which means: ly:text-interface::interpret-markup is the full name,
% for which interpret-markup is just a shorthand.

```