]> git.donarmstrong.com Git - lilypond.git/commitdiff
Issue 3313: Add the command \offset
authorDavid Nalesnik <david.nalesnik@gmail.com>
Fri, 12 Apr 2013 23:40:45 +0000 (18:40 -0500)
committerJanek Warchoł <lemniskata.bernoullego@gmail.com>
Sun, 13 Oct 2013 17:55:08 +0000 (19:55 +0200)
The ability to offset values of various properties would be a useful
enhancement of LilyPond.  Currently, this is possible for default values
of the property 'control-points using the \shape command.  The following
patch seeks to generalize the application of offsets to grob properties.
Both overrides and tweaks are supported, on the model of \shape and
\alterBroken.

Offsets are currently limited to three data types: number, number-pair,
and number-pair-list (this last is defined by this patch and represents
the type used, for example, by 'control-points).  Offsets are limited to
the properties listed in the grob descriptions contained in
`scm/define-grobs.scm'.  Offsets will be reckoned against an override
currently in effect; otherwise, the default setting from the grob
description will be used.

input/regression/offsets.ly [new file with mode: 0644]
ly/music-functions-init.ly
scm/c++.scm
scm/lily.scm
scm/music-functions.scm

diff --git a/input/regression/offsets.ly b/input/regression/offsets.ly
new file mode 100644 (file)
index 0000000..5348972
--- /dev/null
@@ -0,0 +1,73 @@
+\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)
+}
index f47cd1d21422fb57f53c671544cc7bc6f9acbcf5..d33bf9da814ae8e2123fa2ab65d50f21bf817f55 100644 (file)
@@ -694,6 +694,26 @@ octaveCheck =
    (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},
index 8f4986a257ba72fe1ef0cefba10f792062cb2c6f..a131e7f3428d95870bcc3ec05b1643fe7d899552 100644 (file)
   (and (pair? x)
        (number? (car x)) (number? (cdr x))))
 
+(define-public (number-pair-list? x)
+  (and (list? x)
+       (every number-pair? x)))
+
 (define-public (fraction? x)
   (and (pair? x)
        (index? (car x)) (index? (cdr x))))
index 4a63be9908d92cd791746f7435e4030bee19fb45..9e47fc0df7548dcdc55d049f7afbfa7997a882c0 100644 (file)
@@ -662,6 +662,7 @@ messages into errors.")
     (,number-or-pair? . "number or pair")
     (,number-or-string? . "number or string")
     (,number-pair? . "pair of numbers")
+    (,number-pair-list? . "list of number pairs")
     (,rhythmic-location? . "rhythmic location")
     (,scheme? . "any type")
     (,string-or-pair? . "string or pair")
index afcdb843e72545e3f20a1922a737a58a08044577..ccecf4b43d0cef88d43cce97b79d5a3b61449b28 100644 (file)
@@ -2081,3 +2081,95 @@ Broken measures are numbered in parentheses."
                     (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)