--- /dev/null
+\version "2.17.28"
+
+\header {
+ texidoc = "The @code{\\offset} command may be used to displace various properties
+from the default settings contained in grob descriptions. Settings which may be
+offset are limited to those of type @code{number}, @code{number-pair}, or
+@code{number-pair-list}. Most of the following examples begin with the grob in its
+default appearance. The command is demonstrated as a tweak and as an override."
+}
+
+\layout {
+ ragged-right = ##t
+ indent = 0
+}
+
+\relative c' {
+
+ %% ARPEGGIO %%
+ % default
+ <c e g b>1\arpeggio
+ <c e g b>1-\offset #'positions #'(-1 . 1) \arpeggio
+ \bar "||"
+
+ %% BREATHING SIGN %%
+ % default
+ c1 \breathe
+ c1
+ \once \offset #'Y-offset #1 BreathingSign
+ \breathe
+ \bar "||"
+
+ %% DYNAMICS %%
+ % default
+ c1\f
+ \once \offset #'X-offset #-1 DynamicText
+ c1\f
+ % DynamicLineSpanner
+ c1-\offset #'padding #1 \f
+ \bar "||"
+
+ %% BEAMS %%
+ % default
+ c'8 d e f
+ \once \offset #'positions #'(-1 . -1) Voice.Beam
+ c8 d e f
+ % same effect as an offset of '(-2 . -2)
+ \once \offset #'positions #-2 Beam
+ c8 d e f
+ \override Beam.breakable = ##t
+ c8-\offset #'positions #'((-1 . -3) (-3 . -1)) [ d e f
+ \break
+ g8 f e d] c-\offset #'beam-thickness #0.48 [ d e f]
+ \bar "||"
+
+ %% TEXT SPANNERS %%
+ c4\startTextSpan d e f\stopTextSpan
+ \once \offset #'dash-fraction #'(0.1 0.3) TextSpanner
+ \once \offset #'staff-padding #'(1.0 2.0) TextSpanner
+ c4\startTextSpan d e f
+ \break
+ c4 d e f\stopTextSpan
+ \bar "||"
+
+ %% SLURS %%
+ % this duplicates the effect of the \shape command
+ \offset #'control-points #'(
+ ((0 . 0) (0 . 1) (0 . 2) (0 . 1))
+ ((1 . 0) (0 . 4) (0 . 4) (0 . 0))
+ ) Slur
+ c4-\offset #'line-thickness #'(0 10) ( d e f
+ \break
+ c4 d e f)
+}
(make-music 'RelativeOctaveCheck
'pitch pitch))
+offset =
+#(define-music-function (parser location property offsets item)
+ (symbol-list-or-symbol? scheme? symbol-list-or-music?)
+ (_i "Offset the default value of @var{property} of @var{item} by
+@var{offsets}. If @var{item} is a string, the result is
+@code{\\override} for the specified grob type. If @var{item} is
+a music expression, the result is the same music expression with an
+appropriate tweak applied.")
+ (if (ly:music? item)
+ #{ \tweak #property #(offsetter property offsets) #item #}
+ (if (check-grob-path item parser location
+ #:default 'Bottom
+ #:min 2
+ #:max 2)
+ #{
+ \override #item . #property =
+ #(offsetter property offsets)
+ #}
+ (make-music 'Music))))
+
omit =
#(define-music-function (parser location item) (symbol-list-or-music?)
(_i "Set @var{item}'s @samp{stencil} property to @code{#f},
(cdr break-alignment-L-ext))))
X)))
num))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; The following are used by the \offset function
+
+(define (find-value-to-offset prop self alist)
+ "Return the first value of the property @var{prop} in the property alist
+@var{alist} @em{after} having found @var{self}."
+ (let ((segment (member (cons prop self) alist)))
+ (if (not segment)
+ (assoc-get prop alist)
+ (assoc-get prop (cdr segment)))))
+
+(define (offset-multiple-types arg offsets)
+ "Displace @var{arg} by @var{offsets} if @var{arg} is a number, a
+number pair, or a list of number pairs. If @var{offsets} is an empty
+list or if there is a type-mismatch, @var{arg} will be returned."
+ (cond
+ ((and (number? arg) (number? offsets))
+ (+ arg offsets))
+ ((and (number-pair? arg)
+ (or (number? offsets)
+ (number-pair? offsets)))
+ (coord-translate arg offsets))
+ ((and (number-pair-list? arg) (number-pair-list? offsets))
+ (map
+ (lambda (x y) (coord-translate x y))
+ arg offsets))
+ (else arg)))
+
+(define-public (offsetter property offsets)
+ "Apply @var{offsets} to the default values of @var{property} of @var{grob}.
+Offsets are restricted to immutable properties and values of type @code{number},
+@code{number-pair}, or @code{number-pair-list}."
+ (define (self grob)
+ (let* ((immutable (ly:grob-basic-properties grob))
+ ; We need to search the basic-properties alist for our property to
+ ; obtain values to offset. Our search is complicated by the fact that
+ ; calling the music function `offset' as an override conses a pair to
+ ; the head of the alist. This pair must be discounted. The closure it
+ ; contains is named `self' so it can be easily recognized. If `offset'
+ ; is called as a tweak, the basic-property alist is unaffected.
+ (target (find-value-to-offset property self immutable))
+ ; if target is a procedure, we need to apply it to our grob to calculate
+ ; values to offset.
+ (vals
+ (if (procedure? target)
+ (target grob)
+ target))
+ (can-type-be-offset?
+ (or (number? vals)
+ (number-pair? vals)
+ (number-pair-list? vals))))
+
+ (if can-type-be-offset?
+ ; '(+inf.0 . -inf.0) would offset to itself. This will be confusing to a
+ ; user unaware of the default value of the property, so issue a warning.
+ (if (equal? empty-interval vals)
+ (ly:warning "default '~a of ~a is ~a and can't be offset"
+ property grob vals)
+ (let* ((orig (ly:grob-original grob))
+ (siblings
+ (if (ly:spanner? grob)
+ (ly:spanner-broken-into orig)
+ '()))
+ (total-found (length siblings))
+ ; Since there is some flexibility in input syntax,
+ ; structure of `offsets' is normalized.
+ (offsets
+ (if (or (not (pair? offsets))
+ (number-pair? offsets)
+ (and (number-pair-list? offsets)
+ (number-pair-list? vals)))
+ (list offsets)
+ offsets)))
+
+ (define (helper sibs offs)
+ ; apply offsets to the siblings of broken spanners
+ (if (pair? offs)
+ (if (eq? (car sibs) grob)
+ (offset-multiple-types vals (car offs))
+ (helper (cdr sibs) (cdr offs)))
+ vals))
+
+ (if (>= total-found 2)
+ (helper siblings offsets)
+ (offset-multiple-types vals (car offsets)))))
+
+ (begin
+ (ly:warning "the property '~a of ~a cannot be offset" property grob)
+ vals))))
+ ; return the closure named `self'
+ self)