\version "2.15.41"

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The command \offset allows you to add offsets to the default values of a
%% grob's properties.  You can add offsets to properties which are set to
%% a number, a number-pair, or a list of number-pairs.  You can specify
%% offsets for each part of a broken spanner.
%%
%% The command will only work with immutable properties, specifically those
%% set in the file `define-grobs.scm'.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#(define (pair-list? x)
  (and (pair? x)
       (every number-pair? x)))

#(define (offset-general arg offsets)
  (cond
    ((null? offsets) arg)
    ((number? arg) (+ arg offsets))
    ((number-pair? arg) (coord-translate arg offsets))
    ((pair-list? arg)
     (map
       (lambda (x y) (coord-translate x y))
       arg offsets))))

#(define ((offsetter property offsets) grob)
  (let* ((immutable (ly:grob-basic-properties grob))
         (target (assoc-get property (reverse immutable)))
         (vals
           (if (procedure? target)
               (if (procedure-name target) ; check for #<procedure #f (grob)>
                   (target grob)
                   '())
               target)))

    (if (or (number? vals)
            (number-pair? vals)
            (pair-list? vals))

        (let* ((orig (ly:grob-original grob))
               (siblings
                 (if (ly:spanner? grob)
                     (ly:spanner-broken-into orig)
                     '()))
               (total-found (length siblings)))

          (define (helper sibs offs)
            (if (pair? offs)
                (if (eq? (car sibs) grob)
                    (offset-general vals (car offs))
                    (helper (cdr sibs) (cdr offs)))
                vals))

          ;; standardize form of offsets
          (if (or (null? offsets)
                  (and (number? offsets) (number? vals))
                  (and (number-pair? offsets) (number-pair? vals))
                  (and (pair-list? offsets) (pair-list? vals)))
              (set! offsets (list offsets)))

          (if (>= total-found 2)
              (helper siblings offsets)
              (offset-general vals (car offsets))))

        vals)))

offset =
#(define-music-function (parser location name property offsets)
  (string? scheme? scheme?)
  (let* ((name (string-delete name char-set:blank)) ; remove any spaces
         (name-components (string-split name #\.))
         (context-name "Bottom")
         (grob-name #f))

    (if (> 2 (length name-components))
        (set! grob-name (car name-components))
        (begin
          (set! grob-name (cadr name-components))
          (set! context-name (car name-components))))
 #{
   \override $context-name . $grob-name $property =
     #(lambda (grob) ((offsetter property offsets) grob))
 #}))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\relative c'' {
  c2\f
  \once \offset DynamicLineSpanner #'Y-offset #-0.5
  c\f

  c8 d e f
  \once \offset Beam #'positions #'(-1 . -1)
  c8 d e f

  <c e>4\arpeggio
  \once \offset Arpeggio #'positions #'(-0.5 . 0.5)
  <c e>4\arpeggio

  %%%%%%%
  % the following duplicates the effect of the \shape command
  % available from 2.15.40
  c8( d e f)
  \once \offset Slur #'control-points #'((0 . 0) (0 . 1) (0 . 1) (0 . 0))
  c8( d e f)
  \once \offset Slur #'control-points #'(
   ((0 . 0) (0 . 1) (0 . 2) (0 . 1))
   ((1 . 0) (0 . 4) (0 . 4) (0 . 0))
   )
  c8( d e f
  \break
  c8 d e f)
  %%%%%%%

  \once \offset "Staff.OttavaBracket" #'Y-offset #'(1 2)
  \ottava #1
  c'8 d e f
  \break
  c d e f
  \ottava #0

  \override Beam #'breakable = ##t
  \offset Beam #'positions #'((-1 . -2) (-3 . -2))
  c,8[ d e f
  \break
  \offset DynamicText #'X-offset #-2
  g\f f e d] r2
}

\layout {
  ragged-right = ##t
  indent = 0
}
