+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; falls/doits
+
+(define-public (bend::print spanner)
+ (define (close a b)
+ (< (abs (- a b)) 0.01))
+
+ (let* ((delta-y (* 0.5 (ly:grob-property spanner 'delta-position)))
+ (left-span (ly:spanner-bound spanner LEFT))
+ (dots (if (and (grob::has-interface left-span 'note-head-interface)
+ (ly:grob? (ly:grob-object left-span 'dot)))
+ (ly:grob-object left-span 'dot) #f))
+
+ (right-span (ly:spanner-bound spanner RIGHT))
+ (thickness (* (ly:grob-property spanner 'thickness)
+ (ly:output-def-lookup (ly:grob-layout spanner)
+ 'line-thickness)))
+ (padding (ly:grob-property spanner 'padding 0.5))
+ (common (ly:grob-common-refpoint right-span
+ (ly:grob-common-refpoint spanner
+ left-span X)
+ X))
+ (common-y (ly:grob-common-refpoint spanner left-span Y))
+ (minimum-length (ly:grob-property spanner 'minimum-length 0.5))
+
+ (left-x (+ padding
+ (max
+ (interval-end (ly:grob-robust-relative-extent
+ left-span common X))
+ (if
+ (and dots
+ (close
+ (ly:grob-relative-coordinate dots common-y Y)
+ (ly:grob-relative-coordinate spanner common-y Y)))
+ (interval-end
+ (ly:grob-robust-relative-extent dots common X))
+ ;; TODO: use real infinity constant.
+ -10000))))
+ (right-x (max (- (interval-start
+ (ly:grob-robust-relative-extent right-span common X))
+ padding)
+ (+ left-x minimum-length)))
+ (self-x (ly:grob-relative-coordinate spanner common X))
+ (dx (- right-x left-x))
+ (exp (list 'path thickness
+ `(quote
+ (rmoveto
+ ,(- left-x self-x) 0
+
+ rcurveto
+ ,(/ dx 3)
+ 0
+ ,dx ,(* 0.66 delta-y)
+ ,dx ,delta-y)))))
+
+ (ly:make-stencil
+ exp
+ (cons (- left-x self-x) (- right-x self-x))
+ (cons (min 0 delta-y)
+ (max 0 delta-y)))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; grace spacing
+
+(define-public (grace-spacing::calc-shortest-duration grob)
+ (let* ((cols (ly:grob-object grob 'columns))
+ (get-difference
+ (lambda (idx)
+ (ly:moment-sub (ly:grob-property
+ (ly:grob-array-ref cols (1+ idx)) 'when)
+ (ly:grob-property
+ (ly:grob-array-ref cols idx) 'when))))
+
+ (moment-min (lambda (x y)
+ (cond
+ ((and x y)
+ (if (ly:moment<? x y)
+ x
+ y))
+ (x x)
+ (y y)))))
+
+ (fold moment-min #f (map get-difference
+ (iota (1- (ly:grob-array-length cols)))))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; fingering
+
+(define-public (fingering::calc-text grob)
+ (let* ((event (event-cause grob))
+ (digit (ly:event-property event 'digit)))
+
+ (if (> digit 5)
+ (ly:input-message (ly:event-property event 'origin)
+ "Warning: Fingering notation for finger number ~a"
+ digit))
+
+ (number->string digit 10)))
+
+(define-public (string-number::calc-text grob)
+ (let ((digit (ly:event-property (event-cause grob) 'string-number)))
+
+ (number->string digit 10)))
+
+(define-public (stroke-finger::calc-text grob)
+ (let* ((digit (ly:event-property (event-cause grob) 'digit))
+ (text (ly:event-property (event-cause grob) 'text)))
+
+ (if (string? text)
+ text
+ (vector-ref (ly:grob-property grob 'digit-names)
+ (1- (max (min 5 digit) 1))))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; dynamics
+
+(define-public (hairpin::calc-grow-direction grob)
+ (if (eq? (ly:event-property (event-cause grob) 'class) 'decrescendo-event)
+ START
+ STOP))
+
+(define-public (dynamic-text-spanner::before-line-breaking grob)
+ "Monitor left bound of @code{DynamicTextSpanner} for absolute dynamics.
+If found, ensure @code{DynamicText} does not collide with spanner text by
+changing @code{'attach-dir} and @code{'padding}. Reads the
+@code{'right-padding} property of @code{DynamicText} to fine tune space
+between the two text elements."
+ (let ((left-bound (ly:spanner-bound grob LEFT)))
+ (if (grob::has-interface left-bound 'dynamic-text-interface)
+ (let* ((details (ly:grob-property grob 'bound-details))
+ (left-details (ly:assoc-get 'left details))
+ (my-padding (ly:assoc-get 'padding left-details))
+ (script-padding (ly:grob-property left-bound 'right-padding 0)))
+
+ (and (number? my-padding)
+ (ly:grob-set-nested-property! grob
+ '(bound-details left attach-dir)
+ RIGHT)
+ (ly:grob-set-nested-property! grob
+ '(bound-details left padding)
+ (+ my-padding script-padding)))))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; lyrics
+
+(define-public (lyric-text::print grob)
+ "Allow interpretation of tildes as lyric tieing marks."
+
+ (let ((text (ly:grob-property grob 'text)))
+
+ (grob-interpret-markup grob (if (string? text)
+ (make-tied-lyric-markup text)
+ text))))
+
+(define-public ((grob::calc-property-by-copy prop) grob)
+ (ly:event-property (event-cause grob) prop))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; fret boards
+
+(define-public (fret-board::calc-stencil grob)
+ (grob-interpret-markup
+ grob
+ (make-fret-diagram-verbose-markup
+ (ly:grob-property grob 'dot-placement-list))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; scripts
+
+(define-public (script-interface::calc-x-offset grob)
+ (ly:grob-property grob 'positioning-done)
+ (let* ((shift (ly:grob-property grob 'toward-stem-shift 0.0))
+ (note-head-location
+ (ly:self-alignment-interface::centered-on-x-parent grob))
+ (note-head-grob (ly:grob-parent grob X))
+ (stem-grob (ly:grob-object note-head-grob 'stem)))
+
+ (+ note-head-location
+ ;; If the property 'toward-stem-shift is defined and the script
+ ;; has the same direction as the stem, move the script accordingly.
+ ;; Since scripts can also be over skips, we need to check whether
+ ;; the grob has a stem at all.
+ (if (ly:grob? stem-grob)
+ (let ((dir1 (ly:grob-property grob 'direction))
+ (dir2 (ly:grob-property stem-grob 'direction)))
+ (if (equal? dir1 dir2)
+ (let* ((common-refp (ly:grob-common-refpoint grob stem-grob X))
+ (stem-location
+ (ly:grob-relative-coordinate stem-grob common-refp X)))
+ (* shift (- stem-location note-head-location)))
+ 0.0))
+ 0.0))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; instrument names
+
+(define-public (system-start-text::print grob)
+ (let* ((left-bound (ly:spanner-bound grob LEFT))
+ (left-mom (ly:grob-property left-bound 'when))
+ (name (if (moment<=? left-mom ZERO-MOMENT)
+ (ly:grob-property grob 'long-text)
+ (ly:grob-property grob 'text))))
+
+ (if (and (markup? name)
+ (!= (ly:item-break-dir left-bound) CENTER))
+
+ (grob-interpret-markup grob name)
+ (ly:grob-suicide! grob))))
+
+(define-public (system-start-text::calc-x-offset grob)
+ (let* ((left-bound (ly:spanner-bound grob LEFT))
+ (left-mom (ly:grob-property left-bound 'when))
+ (layout (ly:grob-layout grob))
+ (indent (ly:output-def-lookup layout
+ (if (moment<=? left-mom ZERO-MOMENT)
+ 'indent
+ 'short-indent)
+ 0.0))
+ (system (ly:grob-system grob))
+ (my-extent (ly:grob-extent grob system X))
+ (elements (ly:grob-object system 'elements))
+ (common (ly:grob-common-refpoint-of-array system elements X))
+ (total-ext empty-interval)
+ (align-x (ly:grob-property grob 'self-alignment-X 0))
+ (padding (min 0 (- (interval-length my-extent) indent)))
+ (right-padding (- padding
+ (/ (* padding (1+ align-x)) 2))))
+
+ ;; compensate for the variation in delimiter extents by
+ ;; calculating an X-offset correction based on united extents
+ ;; of all delimiters in this system
+ (let unite-delims ((l (ly:grob-array-length elements)))
+ (if (> l 0)
+ (let ((elt (ly:grob-array-ref elements (1- l))))
+
+ (if (grob::has-interface elt 'system-start-delimiter-interface)
+ (let ((dims (ly:grob-extent elt common X)))
+ (if (interval-sane? dims)
+ (set! total-ext (interval-union total-ext dims)))))
+ (unite-delims (1- l)))))
+
+ (+
+ (ly:side-position-interface::x-aligned-side grob)
+ right-padding
+ (- (interval-length total-ext)))))
+
+(define-public (system-start-text::calc-y-offset grob)
+
+ (define (live-elements-list me)
+ (let ((elements (ly:grob-object me 'elements)))
+
+ (filter! grob::is-live?
+ (ly:grob-array->list elements))))
+
+ (let* ((left-bound (ly:spanner-bound grob LEFT))
+ (live-elts (live-elements-list grob))
+ (system (ly:grob-system grob))
+ (extent empty-interval))
+
+ (if (and (pair? live-elts)
+ (interval-sane? (ly:grob-extent grob system Y)))
+ (let get-extent ((lst live-elts))
+ (if (pair? lst)
+ (let ((axis-group (car lst)))
+
+ (if (and (ly:spanner? axis-group)
+ (equal? (ly:spanner-bound axis-group LEFT)
+ left-bound))
+ (set! extent (add-point extent
+ (ly:grob-relative-coordinate
+ axis-group system Y))))
+ (get-extent (cdr lst)))))
+ ;; no live axis group(s) for this instrument name -> remove from system
+ (ly:grob-suicide! grob))
+
+ (+
+ (ly:self-alignment-interface::y-aligned-on-self grob)
+ (interval-center extent))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; ambitus
+
+(define-public (ambitus::print grob)
+ (let ((heads (ly:grob-object grob 'note-heads)))
+
+ (if (and (ly:grob-array? heads)
+ (= (ly:grob-array-length heads) 2))
+ (let* ((common (ly:grob-common-refpoint-of-array grob heads Y))
+ (head-down (ly:grob-array-ref heads 0))
+ (head-up (ly:grob-array-ref heads 1))
+ (gap (ly:grob-property grob 'gap 0.35))
+ (point-min (+ (interval-end (ly:grob-extent head-down common Y))
+ gap))
+ (point-max (- (interval-start (ly:grob-extent head-up common Y))
+ gap)))
+
+ (if (< point-min point-max)
+ (let* ((layout (ly:grob-layout grob))
+ (line-thick (ly:output-def-lookup layout 'line-thickness))
+ (blot (ly:output-def-lookup layout 'blot-diameter))
+ (grob-thick (ly:grob-property grob 'thickness 2))
+ (width (* line-thick grob-thick))
+ (x-ext (symmetric-interval (/ width 2)))
+ (y-ext (cons point-min point-max))
+ (line (ly:round-filled-box x-ext y-ext blot))
+ (y-coord (ly:grob-relative-coordinate grob common Y)))
+
+ (ly:stencil-translate-axis line (- y-coord) Y))
+ empty-stencil))
+ (begin
+ (ly:grob-suicide! grob)
+ (list)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; laissez-vibrer tie
+;;
+;; needed so we can make laissez-vibrer a pure print
+;;
+(define-public (laissez-vibrer::print grob)
+ (ly:tie::print grob))
+