Hi Simon,

On Tue, Jul 21, 2015 at 12:26 PM, Simon Albrecht <[email protected]>
wrote:

> Hello,
>
> while developing a workaround (or solution?) for issue 4316 <
> https://code.google.com/p/lilypond/issues/detail?id=4316>, I encountered
> the following problem: \offset cannot be used on X and Y offset properties,
> apparently because these are mutable, and as the doc string for (offsetter)
> in scm/music-functions.scm states, \offset is limited to work only on
> immutable properties. (I haven’t really gotten behind the distinction of
> mutable/immutable properties…)
> Now, I don’t really know how to circumvent this, and it seems like a
> really important usecase for \offset. Do you have any ideas here?
>
>
The issue of mutable properties is actually only a problem with
Hairpin.X-offset in your example.  You'll note that each of the other
properties you offset has a default value in scm/define-grobs.scm to
calculate a displacement against.

The problem with these other properties is that "unpure-pure containers"
are involved, and the current implementation of \offset has no provision
for dealing with this situation.  If it could, a number of use cases would
be opened up--some very handy ones at that.

It is straightforward to redefine \offset to extract the unpure component
in such cases and use that to offset against.  (See attached.)  I haven't
noticed any problems, but I do wonder if a problem might occur, say, if the
change to the property should properly result in a different line break.

Now, whether it would be feasible to offset against mutable properties I'm
not sure.  I've never had success getting changes to values stored there to
have any effect on output.  Too late in the game?

Hope this helps,
David
\version "2.19.23"

#(define (find-value-to-offset prop self alist)
  "Return the first value of the property @var{prop} in the property
alist @var{alist} -- after having found @var{self}.  If @var{self} is
not found, return the first value of @var{prop}."
  (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
             (cond ((ly:unpure-pure-container? target)
                    ((ly:unpure-pure-container-unpure-part target) grob))
                   ((procedure? target) (target grob))
                   (else 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)

off =
#(define-music-function
  (spec event)
  (pair? ly:music?)
  "Shift grobs using X- and Y-offset, with outside-staff-priority unset by default.

Provides dedicated input modes for Dynamics in order to handle the tricky interaction with DynamicLineSpanner and both settings of outside-staff-priority.
"
  ;; default values
  (define x-off 0)
  (define y-off 0)
  (define inside-staff? #t)
  (define dynamic? #f)
  (define dyn-line-off 0)

  (define (spec-type? spec symbol-list)
    (and (list? spec)
         (member (car spec) symbol-list)))

  ;; Apply tweak only if cond? is true
  (define (cond-tweak cond? prop val arg)
    (if cond? (tweak prop val arg) arg))

  ;; differentiate between spec-types
  (cond
   ((number-pair? spec)
    (set! x-off (car spec))
    (set! y-off (cdr spec)))
   ;; Offset DynamicText and Hairpin.
   ((spec-type? spec '(dynamic dyn d))
    (set! dynamic? #t)
    (set! x-off (second spec))
    (set! y-off (third spec)))
   ;; Offset DynamicLineSpanner; Y-offset only
   ((spec-type? spec '(dynamic-line dyn-line dl))
    ;; Allow requiring outside-staff placement
    ;; by giving #f as an additional element.
    (if (= 3 (length spec)) (set! inside-staff? (third spec)))
    (set! dyn-line-off (second spec)))
   (else (ly:warning "Cannot read specification for offset - using default values.")))

  (cond-tweak inside-staff? 'outside-staff-priority #f
    (offset 'X-offset x-off
      (offset 'Y-offset y-off
        (cond-tweak
         (and dynamic? inside-staff?)
         '(DynamicLineSpanner outside-staff-priority)
         #f
         (offset '(DynamicLineSpanner Y-offset) dyn-line-off event))))))

\relative {
  \hideNotes
  \dynamicUp
  b'2-\off #'(dl 0) -\off #'(d 0 0) \< 2\f\>
  2 2\!\<
  2\f
}
}
_______________________________________________
lilypond-user mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to