One further iteration. Please let me know what you think of the interface:

-\compoundSlur \with {
  % offsets against the automatic control points
  offsets =       
  #'((0 . -1.5)   ; left starting point
      (-2 . -1)   ; second control point
      (2 . -5)    ; second-to-last control point
      (0 . 0))    % right end point

  inflection-ratio =
  #'(0.6 . 0.65)  % X/Y ratio of the inflection point
                  % calculated between the actual end points of the slur
                  % defaults to the center

  % Slope of the line around the inflection point
  % defaults to 1/1
  inflection-slope = 5/3

  % length of the "handles" around the inflection
  % given as a ratio to the left or right baseline
  % (distance between inflection point and slur's end point).
  % If not given they default to the length of the respective handle
  % on the left or right side
  inflection-ratio-right = 0.5
  inflection-ratio-left = 0.25
  
  % Display control points
  ann = ##t         
}

Of course one could use a list of unnamed arguments or an alist instead
of the \with {} clause (I just happen to like them). I have spiced up
the "annotation" even more compared to the last version (prints the
original slur and its control points in light gray in the background),
and I have the impression this approach to defining the inflection point
is quite natural, but I'm open to discussion.

One thing that is still missing from my personal wish list is a flag to
make the center symmetric again.

Urs


Am 19.09.2016 um 11:00 schrieb Urs Liska:
>
>
>
> Am 19.09.2016 um 00:32 schrieb David Kastrup:
>> Urs Liska <u...@openlilylib.org> writes:
>>
>>> Am 18.09.2016 um 20:54 schrieb David Kastrup:
>>>> Do you know how to split a bezier at a given ratio into equivalent
>>>> beziers?  It's a comparatively simple operation and I think it's already
>>>> somewhere in the C++ code though without access from Scheme.
>>> No, but I should be able to figure it out (if noone sends a pointer
>>> before I manage to do so).
>> Well, METAFONT uses the notation
>>
>> a[z1, z2]
>>
>> for z1 + a*[z2-z1], mapping a range of 0..1 for a linearly between z1
>> and z2.
>>
>> If we have points z1, z2, z3, z4 defining a Bezier, then the two split
>> beziers are defined with the points
>>
>> z1, a[z1, z2], a[a[z1, z2], a[z2, z3]], a[a[a[z1, z2], a[z2, z3]],
>>                                           a[a[z2, z3], a[z3, z4]]
>>
>> and
>>
>> a[a[a[z1, z2], a[z2, z3]], a[a[z2, z3], a[z3, z4]]],
>> a[a[z2, z3], a[z3, z4]], a[z3, z4], z4
>>
>> Basically, calculation of a point a on an n-grade Bezier is done using a
>> recursive formula to depth n, and keeping the intermediate results will
>> give you the control points for the Bezier curves split at that point.
>>
>
> I think before diving into that I share what I currently have, so we
> may discuss which approach should actually be continued.
>
> The attached solution does the following:
>
>   * Apply offsets for the start/end points and to the second and
>     second-to-last control-points, based on the original points of the
>     non-compound slur
>   * Add an inflection point, which is specified as a point between the
>     (actual) end points of the slur, given X and Y ratios (as a pair
>     of numbers between 0 and 1
>   * Determine the length and slope of the line going through the
>     inflection point.
>       o Currently this is done through specifying one point relative
>         to the inflection point and mirroring it symmetrically
>       o Instead I'd like to specify an angle and a length.
>       o It would be nice to have the angle relative to the slope of
>         the slur as a whole, but that may not be a good idea, as we
>         have actually two separate lines with different slopes
>       o Length should be given as a ratio, presumable relative to the
>         length of the line between the inflection point and the
>         respective end point.
>       o There should be one optional argument to enforce symmetrical
>         points here.
>
> BTW I've spiced up the control points display a bit. I hope it's
> self-explanatory.
>
> I would like to integrate this with Janek's \shapeII functions
> (https://github.com/openlilylib/snippets/tree/master/notation-snippets/shaping-bezier-curves)
> as I think there'll be quite some code (and interface?) that can be
> shared.
>
> Opinions?
>
> Urs
>
>
>
> _______________________________________________
> lilypond-user mailing list
> lilypond-user@gnu.org
> https://lists.gnu.org/mailman/listinfo/lilypond-user

%%%%%%%%%%%%%%%%%%%%%%%%
\version "2.19.47"
\language "deutsch"

%%%%%%%%%%%%%%%%%%%%%%%
% Display Configuration

#(define col-bg (rgb-color .9 .9 .8))
#(define col-orig-slur cyan)
#(define col-new-slur red)
#(define conn-thickness 0.05)
#(define cross-thickness 0.1)
#(define cross-size 0.2)

%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Geometry helper functions

#(define (mirror-point pt1 pt2)
   "Mirror pt2 against pt1"
   (cons
    (- (* 2 (car pt1)) (car pt2))
    (- (* 2 (cdr pt1)) (cdr pt2))))

#(define (add-points pt1 pt2)
   "Add two points"
   (cons (+ (car pt1) (car pt2))
     (+ (cdr pt1) (cdr pt2))))

#(define (sloped-point slope dist)
   "Create a point with given slope and distance"
   (let 
    ; TODO: Is this too confused, can it be done inline?
    ((factor (* dist (/ 1 (distance '(0 . 0) slope )))))
   (cons
     (* (cdr slope) factor)
     (* (car slope) factor))))

#(define (inflection-point pt1 pt2 ratio)
   "Find a point between two points, giving the X and Y ratio independently"
   (let*
    ((xratio (car ratio))
     (yratio (cdr ratio)))
    (cons
     (+ (* (- 1 xratio) (car pt1)) (* xratio (car pt2)))
     (+ (* (- 1 yratio) (cdr pt1)) (* yratio (cdr pt2))))))

#(define (distance pt1 pt2)
   "Caculate distance between two points"
   (let
    ((square (lambda (x) (* x x))))
    (sqrt
     (+
      (square (- (car pt1) (car pt2)))
      (square (- (cdr pt1) (cdr pt2)))))))

#(define (line-slope pt1 pt2)
   "Determine the slope of a line connecting two points"
   (/ (- (cdr pt2) (cdr pt1)) (- (car pt2) (car pt1))))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Control point visualization

#(define (make-cross-stencil coord col)
   "Draw a cross-stencil at coord."
   (stencil-with-color
    (ly:stencil-add
     (make-line-stencil
      cross-thickness
      (- (car coord) cross-size)
      (- (cdr coord) cross-size)
      (+ (car coord) cross-size)
      (+ (cdr coord) cross-size))
     (make-line-stencil
      cross-thickness
      (- (car coord) cross-size)
      (+ (cdr coord) cross-size)
      (+ (car coord) cross-size)
      (- (cdr coord) cross-size)))
    col))

#(define (connect-dots pt1 pt2 col)
   "Draw a thin line connecting two points"
   (stencil-with-color
    (ly:stencil-add
     (make-line-stencil
      conn-thickness
      (car pt1)
      (cdr pt1)
      (car pt2)
      (cdr pt2)))
    col))


#(define (default-option opts name value)
   "Retrieve an option value or a default"
   (let ((prop (assq name opts)))
     (if prop (cdr prop) value)))

compoundSlur =
#(define-event-function (options)(ly:context-mod?)
   ; TODO: Change inner-segment to
   ; - a ratio (to the length of the line between cp1 and cp4)
   ; - an angle (relative to the same line cp1--cp2)
   (let
    ((proc
      (lambda (grob)
        (let*
         ((opts 
           (map 
            (lambda (o)
              (cons (second o) (third o))) 
            (ly:get-context-mods options)))
          ;; Retrieve options and set defaults
          (offsets
           (default-option opts 'offsets '((0 . 0)(0 . 0)(0 . 0)(0 . 0))))
          (inflection-ratio
           (default-option opts 'inflection-ratio '(.5 . .5)))
          (inflection-slope
           (default-option opts 'inflection-slope '(1 . 1)))
          (ann?
           (default-option opts 'ann #f))
          
          (inflection-ratio-left
           (default-option opts 'inflection-ratio-left #f))
          (inflection-ratio-right
           (default-option opts 'inflection-ratio-right #f))
          ; TODO:
          (inner-segment (assq-ref opts 'inner-segment))
          
          ;; automatic control points of the non-compound slur
          (cps (ly:slur::calc-control-points grob))
          
          ;; add offsets to the four control points
          (cp1 (add-points (first cps) (first offsets)))
          (cp2 (add-points (second cps) (second offsets)))
          (cp6 (add-points (third cps) (third offsets)))
          (cp7 (add-points (fourth cps) (fourth offsets)))

          ;; calculate inflection point and surrounding control points
          (cp4 (inflection-point cp1 cp7 inflection-ratio))
          ;; left hand side length of the inflection
          ;; (if given it is the ratio to the left "baseline"
          ;; otherwise it is the same length as the leftmost control point distance)
          (cp3 (add-points cp4 
                 (sloped-point 
                  inflection-slope 
                  (if inflection-ratio-left
                      (* -1 inflection-ratio-left (distance cp1 cp4))
                      (* -1 (distance cp1 cp2))))))
          ;; right hand side length of the inflection
          (cp5 (add-points cp4 
                 (sloped-point 
                  inflection-slope 
                  (if inflection-ratio-right
                      (* inflection-ratio-right (distance cp4 cp7))
                      (distance cp7 cp6)))))

          (first-spline-stil
           (begin
            (ly:grob-set-property! grob 'control-points (list cp1 cp2 cp3 cp4))
            (ly:slur::print grob)))
          (second-spline-stil
           (begin
            (ly:grob-set-property! grob 'control-points (list cp4 cp5 cp6 cp7))
            (ly:slur::print grob)))
          ;; display original slur and its control points
          (original-slur
           (if ann?
               (apply
                ly:stencil-add
                ;; display control points of the original, non-compound slur
                (append
                 ;; display original slur
                 (list
                  (stencil-with-color
                   (begin
                    (ly:grob-set-property! grob 'control-points cps)
                    (ly:grob-set-property! grob 'layer -1)
                    (ly:slur::print grob))
                   col-bg))
                 (map
                  (lambda (c)
                    (make-cross-stencil c col-orig-slur))
                  cps)
                 ))
               empty-stencil))
          ;; display new control-points and connections
          (crosses
           (if ann?
               (apply
                ly:stencil-add
                (append
                 ;; display actual control points of the compound slur
                 (map
                  (lambda (c)
                    (make-cross-stencil c col-new-slur))
                  (list cp1 cp2 cp3 cp4 cp5 cp6 cp7))
                 ;; display connections between original and offset control points
                 (map
                  (lambda (c1 c2)
                    (connect-dots c1 c2 col-orig-slur))
                  cps
                  (list cp1 cp2 cp6 cp7))
                 ;; display obsolete handles of the original slur
                 (map
                  (lambda (c1 c2)
                    (connect-dots c1 c2 col-bg))
                  (list (first cps) (fourth cps))
                  (list (second cps) (third cps)))
                 ;; display handles indicating the
                 (map
                  (lambda (c1 c2)
                    (connect-dots c1 c2 col-new-slur))
                  (list cp1 cp3 cp4 cp7)
                  (list cp2 cp4 cp5 cp6))
                 ))
               empty-stencil))
          )

         (ly:stencil-add
          original-slur
          first-spline-stil
          second-spline-stil
          crosses
          )))))
    #{ -\tweak stencil $proc ( #}))


upper = \relative {
  \once \override Score.MetronomeMark.Y-offset = 8
  \once \override Score.MetronomeMark.color = #white
  \tempo "X"
  \key d \major
  \clef bass
  s2 r8 d,16 g h d g h
  d8 r s2.
  s4 \voiceTwo h8.(-- c16-- h2--)
}
lower = \relative {
  \key d \major
  \clef bass
  r2
  <d' h g=>~-^
  % Uncomment one out of the following lines to compare the
  % default slur with the compound one
  %(
  %%{
  -\compoundSlur \with {
    offsets =       % offsets against the automatic control points
    % of the original, non-compound slur
    #'((0 . -1.5)   ; left starting point
        (-2 . -1)   ; second control point
        (2 . -5)    ; second-to-last control point
        (0 . 0))    % right end point

    inflection-slope = 5/3
    inflection-ratio-right = 0.5
    inflection-ratio-left = 0.25
    
    inflection-ratio =
    #'(0.6 . 0.65)  % X/Y ratio of the inflection point
    % calculated between the actual end points of the slur

    % TODO: change to ratio and angle props (then create defaults)
    inner-segment =
    #'(4 . 6)       % X/Y of the (so far symmetric) line around the
    % inflection point
    ann = ##t 	    % Display control points
  }
  %}
  <<
    {
      <d h g>4
      % Uncomment one of the following lines to see the robustness of the slur
      <c g e>4 \(
      %      <c g e>16 q q q
      <h g>4 \voiceOne cis \)
      \change Staff = upper
      \clef treble \voiceOne d e fis2 )
      \fermata
    }
    \new Voice {
      \voiceTwo
      s2. <g,= e>4 \oneVoice
      <fis h,> <g e> <fis dis>2\fermata
    }
  >>
}
\score {
  <<
    \new PianoStaff <<
      \new Staff = upper \upper
      \new Staff = lower \lower
    >>
  >>
}
%%%%%%%%%%%%%%%%%%%%%%%

Attachment: compound-slur-relative.preview.pdf
Description: Adobe PDF document

_______________________________________________
lilypond-user mailing list
lilypond-user@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to