[Top][All Lists]

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

Re: Pedal gradual release

From: Aaron Hill
Subject: Re: Pedal gradual release
Date: Tue, 22 Jan 2019 05:23:34 -0800
User-agent: Roundcube Webmail/1.3.8

On 2019-01-20 5:16 am, Andrew Bernard wrote:
Hi Aaron,

What we really want is a lovely curved bezier spline! I appreciate your
suggestion, but I personally would rather code PostScript than hack
hairpins to be pedal dynamics. I went through all that when I tried to do
this by hijacking text spanners, and it was never satisfactory.

The desire for nice gradual release curves was what led to a PostScript

I have been meaning to find a non-trivial project to allow me to dig into LilyPond and Scheme more than I usually do. Your use case of gradual piano pedaling seemed a good fit. As such, do not feel compelled at all to make use of this. Certainly, it would be nice if it were actually used; but this has been a good learning exercise all the same.

And I should definitely note this is still a work-in-progress:

\version "2.19.82"

#(define ((gradual-piano-pedal-bracket shape) grob)
  (define (make-dashed-curve-stencil thick left right knots step)
    (define (curve param)
      (let ((param (/ (- param left) (- right left)))
            (degree (- (length knots) 1))
            (points (list-copy knots)))
        (for-each (lambda (outer) (for-each (lambda (inner)
(list-set! points inner (+ (* (- 1 param) (list-ref points (- inner 1))) (* param (list-ref points inner)))))
          (iota (+ (- degree outer) 1) degree -1))) (iota degree 1))
        (list-ref points degree)))
    (define (nearest-even num) (* 2 (inexact->exact (floor (/ num 2)))))
    (let* ((steps (nearest-even (/ (- right left) step)))
           (step (/ (- right left) (- steps 1)))
           (x (iota steps left step))
           (y (map curve x)))
      (apply ly:stencil-add (map (lambda (n)
        (make-line-stencil thick (list-ref x n) (list-ref y n)
(list-ref x (+ 1 n)) (list-ref y (+ 1 n)))) (iota (/ steps 2) 0 2)))))
  (let* ((stencil (ly:piano-pedal-bracket::print grob))
         (width (interval-length (ly:stencil-extent stencil X)))
         (height (ly:grob-property grob 'edge-height '(1 . 1)))
         (layout (ly:grob-layout grob))
         (thick (ly:output-def-lookup layout 'line-thickness))
         (x-scale (- width thick))
         (shape-left (map (lambda (arg) (* (first arg) x-scale)) shape))
         (shape-right (append (drop shape-left 1) (list x-scale)))
         (shape-knots (map (lambda (arg) (drop arg 1)) shape)))
    (if (grob::is-live? grob) (ly:stencil-add stencil
      (apply ly:stencil-add
        (filter-map (lambda (left right knots)
            (and (< 0 (length knots))
(make-dashed-curve-stencil thick left right knots 0.25)))
          shape-left shape-right shape-knots)))

#(define (number-list-list? arg) (and (list? arg) (every number-list? arg)))
gradualSustain = #(define-event-function (shape) (number-list-list?)
  #{ -\tweak stencil #(gradual-piano-pedal-bracket shape) \sustainOn #})

  \new PianoStaff
    \new Staff { \clef treble \repeat unfold 8 c''4 }
    \new Staff { \clef bass \repeat unfold 8 c4 }
  \new Dynamics
    \with {
      pedalSustainStyle = #'bracket
      \override PianoPedalBracket.edge-height = #'(2 . 3)
{ s4 s4*6\gradualSustain #'((0 2 2 0)(0.3)(0.4 0 5 -3 3)) s4\sustainOff }


\gradualSustain replaces \sustainOn, although a \sustainOff is still needed to terminate the bracket. The parameter to \gradualSustain indicates the desired shape as a list of curve segments. The first value in each segment indicates the left edge of the segment, specified as a fraction of the total bracket width. The right edge is determined by the next segment in the list.

When included, the remaining values in each segment list indicate the desired height of the curve along the segment using an appropriately ordered Bezier. There is no limit on the number values you can provide, as the code above computes Beziers of arbitrary order.

Breaking down the example above we have first a quadratic Bezier going from 0% to 30% along the horizontal that begins at two staff spaces above the base and ends at the bracket base. The second entry in the list omits height values which is shorthand for no curve but it serves to terminate the first curve. Finally, the last curve starts at 40% and continues to the end using a cubic Bezier.


There are still several improvements I plan on making:

- Broken pedal brackets are not supported, as it tries to apply the same curve to both parts.

- Curve segments are not connected visually if there is a discontinuity. I presume a solid vertical line would be sufficient to join the broken ends, similar to how the ends meet the base of the pedal bracket. However, it might make sense for the vertical to be dashed as well.

- The dash width is hard-coded, but ideally should inherit the 'dash-fraction and 'dash-period properties or otherwise mimic existing behavior. Also, I did not account for line thickness, so the gaps are too short.

- The dashes are independently computed per segment, so there is non-uniformity as the algorithm attempts to have a perfect number of dashes. I should probably average the ideal step sizes to ensure consistency throughout the whole shape, although I suspect I will have to fudge a little when curves meet. In that situation, the end points of the touching curves should probably only have each one half of a dash, so the two halves will appear as one.

- The dashes are computed based only on horizontal distance, which means vertical movement stretches the dashes. The actual arc length should be factored in when computed the number of steps to ensure consistency through each arc.

-- Aaron Hill

Attachment: gradual-pedal.cropped.png
Description: PNG image

reply via email to

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