\version "2.19.82" %% Thanks to Aaron Hill %% http://lists.gnu.org/archive/html/lilypond-user/2019-04/msg00240.html %% Does not work for 2.18.2 because of %% - grob::name (could be replaced by grob-name, see p.e. LSR) %% - minimum-length-after-break (no direct replacement possible, only used in %% the examples, though) #(ly:load "bezier-tools.scm") #(define (note-column-bounded? dir grob) "Checks wether @var{grob} is a spanner and whether the spanner is bounded in @var{dir}-direction by a note-column." (if (ly:spanner? grob) (grob::has-interface (ly:spanner-bound grob dir) 'note-column-interface) #f)) #(define (offset-number-pair-list l1 l2) "Offset the number-pairs of @var{l1} by the matching number-pairs of @var{l2}" ;; NB no type-checking or checking for equal lengths is done here (map (lambda (p1 p2) (offset-add p1 p2)) l1 l2)) #(define (bezier::point control-points t) "Given a Bezier curve of arbitrary degree specified by @var{control-points}, compute the point at the specified position @var{t}." (if (< 1 (length control-points)) (let ((q0 (bezier::point (drop-right control-points 1) t)) (q1 (bezier::point (drop control-points 1) t))) (cons (+ (* (car q0) (- 1 t)) (* (car q1) t)) (+ (* (cdr q0) (- 1 t)) (* (cdr q1) t)))) (car control-points))) #(define (bezier::angle control-points t) "Given a Bezier curve of arbitrary degree specified by @var{control-points}, compute the slope at the specified position @var{t}." (let ((q0 (bezier::point (drop-right control-points 1) t)) (q1 (bezier::point (drop control-points 1) t))) (ly:angle (- (car q1) (car q0)) (- (cdr q1) (cdr q0))))) #(define* (bezier::approx-control-points-to-length control-points dir length #:optional (precision 0.01) (right-t 0.2) (left-t 0.8)) "Given a Bezier curve specified by @var{control-points}, return new control-points where the length of the Bezier specified by them is approx @var{length}. The procedure returns if difference of the new calculated length and the given @var{length} is lower than optional @var{precision}. The optional @var{left-t} and @var{right-t} represent the steps where new control-points are calculated relying on @var{dir}." ;; TODO ;; Do the values for precision, left-t, right-t cover all cases? (let* ((frst-cp (car control-points)) (last-cp (last control-points)) (actual-length (ly:length (- (car frst-cp) (car last-cp)) (- (cdr frst-cp) (cdr last-cp)))) (diff (- (abs actual-length) (abs length)))) (if (< diff precision) control-points (bezier::approx-control-points-to-length (if (positive? dir) (cdr (split-bezier control-points right-t)) (car (split-bezier control-points left-t))) dir length)))) #(define (bezier::adjusted-arrow-head dir control-points) (lambda (curve) "Returns a stencil build from an arrowhead-glyph, adjusted to fit at start/end of a curve looking at the curve's @var{control-points}. Relying on @var{dir} for looking at left or right side of the curve." (if (not dir) empty-stencil (let* ((staff-space (ly:staff-symbol-staff-space curve)) ;; reducing fs-from-staff-space a bit looks nicer (fs-from-staff-space (1- (magnification->font-size staff-space))) (grob-font (ly:paper-get-font (ly:grob-layout curve) `(((font-encoding . fetaMusic) (font-size . ,fs-from-staff-space))))) (arrowhead-stil (ly:font-get-glyph grob-font (format #f "arrowheads.open.0~a1" (if (positive? dir) "" "M")))) (arrowhead-width (interval-length (ly:stencil-extent arrowhead-stil X))) (offset-stil (ly:stencil-translate arrowhead-stil (cons (* dir 0.4 arrowhead-width) 0))) (arrowhead-end (interval-bound (ly:stencil-extent offset-stil X) (- dir))) (offset (* 0.33 arrowhead-end)) (angle (bezier::angle (bezier::approx-control-points-to-length control-points dir offset) (if (positive? dir) 0 1)))) (ly:stencil-rotate-absolute offset-stil angle 0 0))))) #(define modify-control-points-for-arrows (lambda (grob) "Returns a number-pair-list suitable for setting @code{control-points}-property. The values are modified with respect to a probably printed arrowhead, which is done by looking at the subproperties of @code{details}: @code{arrow-left} and @code{arrow-right}." (let* ((curve-dir (ly:grob-property grob 'direction)) (details (ly:grob-property grob 'details)) (arrow-left (assoc-get 'arrow-left details #f)) (arrow-right (assoc-get 'arrow-right details #f)) (nc-right-bound? (note-column-bounded? RIGHT grob)) (nc-left-bound? (note-column-bounded? LEFT grob)) (c-ps (ly:grob-property grob 'control-points))) ;; numerical values are my choice -- harm (cond ((and (not arrow-left) (not arrow-right)) c-ps) ((eq? (grob::name grob) 'LaissezVibrerTie) (if arrow-left ;; move a little to right (offset-number-pair-list c-ps '((0.3 . 0) (0.3 . 0) (0.3 . 0) (0.3 . 0))) c-ps)) ((eq? (grob::name grob) 'RepeatTie) (if arrow-right ;; move a little to left (offset-number-pair-list c-ps '((-0.3 . 0) (-0.3 . 0) (-0.3 . 0) (-0.3 . 0))) c-ps)) (else ;; Tie, Slur, PhrasingSlur (let ((move-this-to-left (if arrow-left (if nc-left-bound? 0.4 0.5) 0)) (move-this-to-right (if arrow-right (if nc-right-bound? -0.4 -0.5) 0)) ;; For Ties we want to keep a horizontal look (move-Y-at-left (if (or arrow-left (grob::has-interface grob 'tie-interface)) (* 0.2 curve-dir) 0)) (move-Y-at-right (if (or arrow-right (grob::has-interface grob 'tie-interface)) (* 0.2 curve-dir) 0))) (offset-number-pair-list c-ps (list (cons move-this-to-left move-Y-at-left) (cons move-this-to-left move-Y-at-left) (cons move-this-to-right move-Y-at-right) (cons move-this-to-right move-Y-at-right))))))))) #(define add-arrow-head-to-curve (lambda (grob) "Returns a curve stencil with optional arrowheads at start/end. Whether to print arrowheads is decided by looking at the subproperties of @code{details}: @code{arrow-left} and @code{arrow-right}." (let* ((control-points (modify-control-points-for-arrows grob)) (details (ly:grob-property grob 'details)) (details-arrow-left (assoc-get 'arrow-left details #f)) (details-arrow-right (assoc-get 'arrow-right details #f)) (arrow-left (if (procedure? details-arrow-left) (details-arrow-left grob) details-arrow-left)) (arrow-right (if (procedure? details-arrow-right) (details-arrow-right grob) details-arrow-right))) (if (and (not arrow-left) (not arrow-right)) ;; we're setting 'after-line-breaking, thus do nothing for no arrows '() (let* ((frst (car control-points)) (frth (cadddr control-points)) (function (assoc-get 'stencil (reverse (ly:grob-basic-properties grob)))) (stil ;; Ugh, is there no better way to test that a grob has no ;; 'stencil and that no other previous procedure assigned ;; a stencil-value to said grob? (if (and (procedure? function) (not (eq? (procedure-name function) 'add-arrow-head-to-curve))) (begin (ly:grob-set-property! grob 'control-points control-points) (function grob)) (begin (ly:warning "~a has no stencil. Ignoring." grob) #f))) (arrow-right-stil (if arrow-right ((bezier::adjusted-arrow-head RIGHT control-points) grob) empty-stencil)) (arrow-left-stil (if arrow-left ((bezier::adjusted-arrow-head LEFT control-points) grob) empty-stencil))) (ly:grob-set-property! grob 'stencil (ly:stencil-add (ly:stencil-translate arrow-left-stil frst) (ly:stencil-translate arrow-right-stil frth) stil))))))) pointing-curve = #(define-music-function (curve) (string?) "Set property @code{after-line-breaking} for grob @code{curve}. Finally setting the @code{stencil} to @code{arrowed-curve}. It's needed to go for @code{after-line-breaking}, otherwise changes to @code{control-points} done by @code{shape} wouldn't be respected. Whether or not arrows are printed should done by applying, p.e. @lilypond[verbatim,quote] \\override Tie.details.arrow-left = ##t \\override Slur.details.arrow-left = ##t @end lilypond separately." #{ \temporary \override $curve . after-line-breaking = #add-arrow-head-to-curve #}) revert-pointing-curve = #(define-music-function (curve) (string?) "Revert the setting for @code{after-line-breaking} of grob @var{curve}." #{ \revert $curve . after-line-breaking #}) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% EXAMPLES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \layout { \override Tie.details.arrow-left = ##t \override Slur.details.arrow-left = ##t \override PhrasingSlur.details.arrow-left = ##t \override RepeatTie.details.arrow-left = ##t \override LaissezVibrerTie.details.arrow-left = ##t \override Tie.details.arrow-right = ##t \override Slur.details.arrow-right = ##t \override PhrasingSlur.details.arrow-right = ##t \override RepeatTie.details.arrow-right = ##t \override LaissezVibrerTie.details.arrow-right = ##t %% Two possibilities to limit printing of arrows for broken spanner %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% \alterBroken %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %\alterBroken details.arrow-right #(list #f #f #t) Slur %\alterBroken details.arrow-left #(list #t #f #f) Slur %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Setting details.arrow-right to a procedure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %\override Slur.details.arrow-right = % #(lambda (grob) % (let* ((orig (if (ly:spanner? grob) % (ly:grob-original grob) % #f)) % (siblings (if (ly:grob? orig) % (ly:spanner-broken-into orig) % '()))) % ;; print arrow-right for unbroken or last part of a broken Slur % (if (or (not (pair? siblings)) % (and (pair? siblings) % (equal? grob (car (last-pair siblings))))) % #t % #f))) } % { \new Staff \with { instrumentName = "Slurs" } \relative c'' { \pointing-curve Slur c'1( c \break c \break c) \slurDown c4( c c c) \voiceOne c,,4( c c c'') <>^"default" \revert-pointing-curve Slur \oneVoice c( c c c) } %} % { m = { c4( d e f e d des c) } testI = { \relative c \m \relative c' \m \relative c'' \m \relative c''' \m } \new Staff \with { instrumentName = "Slurs" } { \pointing-curve Slur <>^"no Slur-Stem-direction" \testI \break <>^"Slur down, Stem up" \slurDown \stemUp \testI \break <>^"Slur up, Stem down" \slurUp \stemDown \testI \break <>^"Slur up, Stem up" \slurUp \stemUp \testI \break <>^"Slur down, Stem down" \slurDown \stemDown \testI \break <>^"default" \stemNeutral \slurNeutral \revert-pointing-curve Slur \testI \break } %} % { \new Staff \with { instrumentName = "Ties" } \relative c' { \pointing-curve Tie %% overriding TieColumn.tie-configuration works 1~ \once \override TieColumn.tie-configuration = #'((3.0 . 1) (-1.0 . 1) (-5.0 . -1) (-8.0 . -1)) q \once \override Tie.minimum-length-after-break = 8 1~ \break q <>^"default" \revert-pointing-curve Tie 1~ q } %} % { \new Staff \with { instrumentName = "PhrasingSlur" } \relative c' { \pointing-curve PhrasingSlur 1^\( q q \) <>^"default" \revert-pointing-curve PhrasingSlur 1^\( q q \) } %} % { %% \shape works \new Staff \with { instrumentName = "RepeatTie" } \relative c' { \pointing-curve RepeatTie c1\repeatTie %% If left _and_ right arrow is wished, the RepeatTie may be too %% short, use \shape then <>^"shaped" \shape #'((-0.6 . 0) (-0.6 . -0.1) (0 . -0.1) (0 . 0)) RepeatTie c1\repeatTie <>^"default" \revert-pointing-curve RepeatTie c1\repeatTie } %} % { \new Staff \with { instrumentName = "LaissezVibrerTie" } \relative c' { \pointing-curve LaissezVibrerTie c1\laissezVibrer %% If left _and_ right arrow is wished, the LaissezVibrerTie may be too %% short, use \shape then <>^"shaped" c1-\shape #'((0 . 0) (0 . -0.1) (0.6 . -0.1) (0.6 . 0))-\laissezVibrer <>^"default" \revert-pointing-curve LaissezVibrerTie c1\laissezVibrer } %} \paper { indent = 30 } #(set-global-staff-size 18) %% time values on my machine %% arrow-slur-05.ly %% real 0m4,855s %% user 0m4,376s %% sys 0m0,456s %% arrow-slur-04.ly %% real 0m3,880s %% user 0m3,595s %% sys 0m0,286s %% arrow-slur-03.ly %% real 0m3,540s %% user 0m3,323s %% sys 0m0,216s %% arrow-slur-03-patch.ly %% real 0m4,191s %% user 0m3,776s %% sys 0m0,414s