lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Breaking slurs at apex


From: Lukas-Fabian Moser
Subject: Re: Breaking slurs at apex
Date: Mon, 26 Apr 2021 19:28:25 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.7.1

Hi Michael,

​Well, I only needed it for over a single break for a figure in a dissertation that I'm turning in today, so it's good enough for the moment.

Actually, "the math" is already in LilyPond, as part of Mike Solomon's bezier tools. Unfortunately, they are not defined as publicly available, so I copied them. (Is there a better way to make the definitions of bezier-tools.scm available? Of course I can #(load ...) them, but I'm not certain if this can be done independently of my path configuration.)

split-bezier enables us to take, for instance, the left half of a slur. Then it's just a matter of scaling it to its original width, which leads to:

\version "2.22.0"

% copied from bezier-tools.scm
#(define (coord- coord1 coord2)
   "Subtract @var{coord2} from @var{coord1}."
   (cons (- (car coord1) (car coord2))
         (- (cdr coord1) (cdr coord2))))

#(define (coord* scalar coord)
   "Multiply each component of @var{coord} by @var{scalar}."
   (cons (* (car coord) scalar)
         (* (cdr coord) scalar)))

#(define (coord+ coord1 coord2)
   "Add @var{coord1} to @var{coord2}, returning a coordinate."
   (cons (+ (car coord1) (car coord2))
         (+ (cdr coord1) (cdr coord2))))

#(define (interpolated-control-points control-points split-value)
   "Interpolate @var{control-points} at @var{split-value}.  Return a
set of control points that is one degree less than @var{control-points}."
   (if (null? (cdr control-points))
       '()
       (let ((first (car control-points))
             (second (cadr control-points)))
         (cons* (coord+ first (coord* split-value (coord- second first)))
                (interpolated-control-points
                 (cdr control-points)
                 split-value)))))

#(define (split-bezier bezier split-value)
   "Split a cubic bezier defined by @var{bezier} at the value
@var{split-value}.  @var{bezier} is a list of pairs; each pair is
is the coordinates of a control point.  Returns a list of beziers.
The first element is the LHS spline; the second
element is the RHS spline."
   (let* ((quad-points (interpolated-control-points
                        bezier
                        split-value))
          (lin-points (interpolated-control-points
                       quad-points
                       split-value))
          (const-point (interpolated-control-points
                        lin-points
                        split-value))
          (left-side (list (car bezier)
                           (car quad-points)
                           (car lin-points)
                           (car const-point)))
          (right-side (list (car const-point)
                            (list-ref lin-points 1)
                            (list-ref quad-points 2)
                            (list-ref bezier 3))))
     (cons left-side right-side)))

% ------------------------------------------------------------ %

#(define (affine-x-transform factor offset)
   ; constructs a function applying x |-> factor * x + offset
   ; to the car of a pair
   (lambda (pair)
     (cons
      (+ offset (* factor (car pair)))
      (cdr pair))))

#(define (bezier-x-zoom-to-extent bezier x-extent)
   ; interpolates bezier such that:
   ; the first control point will have x-coordinate (car x-extent),
   ; the last control point will have x-coordinate (cdr x-extent).
   (let* ((old-span (- (car (fourth bezier)) (car (first bezier))))
          (new-span (- (cdr x-extent) (car x-extent)))
          (factor (/ new-span old-span))
          (offset (- (car x-extent) (* factor (car (first bezier))))))
     (map (affine-x-transform factor offset) bezier)))

#(define (horizontalise-broken-slurs grob)
   (let*
    ((orig (ly:grob-original grob))
     (siblings (if (ly:grob? orig)
                   (ly:spanner-broken-into orig)
                   '())))

     (if (>= (length siblings) 2)
         (let*
          ((control-points (ly:grob-property grob 'control-points))
           (x-extent (cons (car (first control-points))
                           (car (fourth control-points))))
           (half-slurs (split-bezier control-points 1/2))
           (new-control-points
            (cond
             ((eq? (first siblings) grob)
              (bezier-x-zoom-to-extent (car half-slurs) x-extent))
             ((eq? (last siblings) grob)
              (bezier-x-zoom-to-extent (cdr half-slurs) x-extent))
             (else control-points))))

           (ly:grob-set-property! grob 'control-points new-control-points)))))

\relative {
  \override Slur.after-line-breaking = #horizontalise-broken-slurs
  c'2 c( e) c c c c'1 ( \break d, d2) 2 2 2 2 2( \break
  \repeat unfold 8 c4 \break 2)
}

Of course this relies on the "standard" broken slurs being symmetrical.

​​Cracking me up though, you and Jean with your "a little trig" and "just do the math."

:-) I guess a large percentage of people on this list have some sort of maths/computer science/... background - which in my case is a bit rusty, so I was glad that the hard part had already been done by Mike.

Granted, "a little trig" might scare away most musicians (or people in general, for that matter), but if you restrict to the select subset of musicians willing to delve into a text-based music typesetting system distantly related to LaTeX and using a dialect of Lisp as its natural extension language, that would seem to change the odds...

Lukas


reply via email to

[Prev in Thread] Current Thread [Next in Thread]