|
| 1 | +\version "2.19.50" |
| 2 | + |
| 3 | +\header { |
| 4 | + snippet-title = "Pedal Decorations" |
| 5 | + snippet-author = "Andrew Bernard" |
| 6 | + snippet-author-email = "andrew.bernard@gmail.com" |
| 7 | + snippet-source = "" |
| 8 | + snippet-description = \markup { |
| 9 | + Add arbitrary cautionary text to pedal brackets on the left hand side, |
| 10 | + and optionally continuation arrows on the right hand side as well. |
| 11 | + } |
| 12 | + tags = "pedal, bracket, arrow, cautionary" |
| 13 | + status = "ready" |
| 14 | +} |
| 15 | + |
| 16 | + |
| 17 | +% Pedal bracket decorations - text on LHS and arrows on RHS. |
| 18 | +% Andrew Bernard |
| 19 | +% With thanks to Thomas Morley for spanner bounds code. |
| 20 | + |
| 21 | +pedalWithArrowsAndTextCallback = |
| 22 | +#(define-scheme-function (lhs-text use-arrows) |
| 23 | + (string? boolean?) |
| 24 | + "lhs-text - text to decorate LHS of bracket. |
| 25 | + use-arrows - boolean: use arrows on RHS if true." |
| 26 | + |
| 27 | + (define (make-arrow-path arrow-length arrowhead-height arrowhead-width) |
| 28 | + "Draw arrow with triangular arrowhead." |
| 29 | + (list |
| 30 | + 'moveto 0 0 |
| 31 | + 'lineto arrow-length 0 |
| 32 | + 'lineto arrow-length (/ arrowhead-width 2) |
| 33 | + 'lineto (+ arrow-length arrowhead-height) 0 |
| 34 | + 'lineto arrow-length (- (/ arrowhead-width 2)) |
| 35 | + 'lineto arrow-length 0 |
| 36 | + 'closepath |
| 37 | + )) |
| 38 | + |
| 39 | + (lambda (grob) |
| 40 | + |
| 41 | + ;; function to modify the individual grob part |
| 42 | + (define add-decorations |
| 43 | + (lambda (g list-length) |
| 44 | + (let* ( |
| 45 | + ;; unpack the argument |
| 46 | + (index (car g)) |
| 47 | + (grobber (cadr g)) |
| 48 | + (last (= index list-length)) |
| 49 | + |
| 50 | + ;; get the default-stencil and its x-dimension and x-length. |
| 51 | + (stil (ly:piano-pedal-bracket::print grobber)) |
| 52 | + (stil-x-extent (ly:stencil-extent stil X)) |
| 53 | + (stil-x-length (interval-length stil-x-extent)) |
| 54 | + |
| 55 | + ;; make arrow for the rhs end |
| 56 | + (new-stil (if (and use-arrows (not last)) |
| 57 | + (begin |
| 58 | + (let* ( |
| 59 | + (thickness 0.1) |
| 60 | + (arrowhead-height 1.0) |
| 61 | + (arrowhead-width 1.0) |
| 62 | + (arrow-length 1.0) |
| 63 | + (arrow |
| 64 | + (make-path-stencil |
| 65 | + (make-arrow-path |
| 66 | + arrow-length |
| 67 | + arrowhead-height |
| 68 | + arrowhead-width) |
| 69 | + thickness 1 1 #t))) |
| 70 | + (ly:stencil-combine-at-edge stil X RIGHT arrow -2))) |
| 71 | + stil)) |
| 72 | + |
| 73 | + ;; make text for the lhs end |
| 74 | + (text-stil |
| 75 | + (grob-interpret-markup grobber |
| 76 | + (markup |
| 77 | + #:line |
| 78 | + (#:abs-fontsize |
| 79 | + 6 |
| 80 | + (#:sans |
| 81 | + (#:upright |
| 82 | + (#:whiteout (#:box (#:pad-markup 0.3 lhs-text))))))))) |
| 83 | + |
| 84 | + ;; get a list of spanners bounded by PianoPedalBrackets |
| 85 | + ;; left-bound, which is PaperColumn or NonMusicalPaperColumn |
| 86 | + (left-bound-spanners |
| 87 | + (ly:grob-array->list |
| 88 | + (ly:grob-object |
| 89 | + (ly:spanner-bound grobber LEFT) |
| 90 | + 'bounded-by-me))) |
| 91 | + |
| 92 | + ;; filter left-bound-spanners for PianoPedalBrackets |
| 93 | + (piano-pedal-brackets |
| 94 | + (filter |
| 95 | + (lambda (gr) |
| 96 | + (grob::has-interface gr 'piano-pedal-bracket-interface)) |
| 97 | + left-bound-spanners)) |
| 98 | + |
| 99 | + ;; delete identical PianoPedalBracket from piano-pedal-brackets |
| 100 | + ;; TODO `delete-duplicates' may be expensive, see guile-manual |
| 101 | + ;; find another method |
| 102 | + (bounded-piano-brackets-per-column |
| 103 | + (delete-duplicates piano-pedal-brackets)) |
| 104 | + |
| 105 | + ;; only add text-stil, if current Column does not have two |
| 106 | + ;; PianoPedalBrackets |
| 107 | + ;; TODO is this condition really sufficient? |
| 108 | + (new-stil |
| 109 | + (if (= (length bounded-piano-brackets-per-column) 2) |
| 110 | + new-stil |
| 111 | + (ly:stencil-stack new-stil X LEFT text-stil -6)))) |
| 112 | + |
| 113 | + (ly:grob-set-property! grobber 'stencil new-stil)))) |
| 114 | + |
| 115 | + (let* ( |
| 116 | + ;; get broken pieces, or the single unbroken grob |
| 117 | + (orig (ly:grob-original grob)) |
| 118 | + (pieces (ly:spanner-broken-into orig)) |
| 119 | + (pieces (if (null? pieces) |
| 120 | + (list orig) |
| 121 | + pieces)) |
| 122 | + (pieces-indexed-list (zip (iota (length pieces) 1) pieces)) |
| 123 | + (pieces-length (length pieces))) |
| 124 | + |
| 125 | + ;; We want arrows on all segments but the last, and text on all segments, |
| 126 | + ;; so we have to pass some notion of list index to the function doing the |
| 127 | + ;; decorating. Hence the ziplist combining grob segment and index in pairs. |
| 128 | + |
| 129 | + (let loop ((count 0)) |
| 130 | + (if (< count pieces-length) |
| 131 | + (begin |
| 132 | + (add-decorations (list-ref pieces-indexed-list count) pieces-length) |
| 133 | + (loop (+ count 1)))))))) |
| 134 | + |
0 commit comments