lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: How to use optional arguments / parameters in a define-markup-comman


From: Urs Liska
Subject: Re: How to use optional arguments / parameters in a define-markup-command
Date: Mon, 04 Nov 2019 23:45:45 +0000

Hi Karsten,

4. November 2019 17:59, "Karsten Reincke" <address@hidden> schrieb:

> Dear friends
> 
> A few days ago, I asked whether anyone knows how one can use variable 
> parameter
> lists = optional arguments = flexible argumentlists in a markup function. I 
> was
> told that such an opportunity does not exist (Thomas Morley) resp. must be
> simulated by some embedded property features (Urs Liska). Many thanks for 
> these
> quick answers. They preserved me from doing unnecessary work.
> 
> Here is a pure scheme / guile solution using lisp association lists (demoed by
> using the box example from the extend tutorial). 

I see one major issue with your approach: it pollutes the namespace with a 
multitude of global variables.

I don't want to push you towards using openLilyLib ;-) but oll-core includes a 
macro "with-options" (created by Stéfano Troncaro) that allows the modification 
of define-music-function (I'm not sure about markup functions right now) so 
that the function accepts an optional \with { } clause, performs validation 
against mandatory and optional arguments (optionally along with their types) 
and can provide default values - all without necessarily using #(define XXX) 
and creating global variables.

The code starts here: 
https://github.com/openlilylib/oll-core/blob/master/internal/options.ily#L245

and here is an example function that is defined that way (all my functions 
using that macro are still in private repositories). It prints text centered 
above and below a given music (in my case it was a measure.

The define-music-function clause is modified by the with-option macro 
(available after \include "oll-core/package.ily").
Then there's one additional list where the options are defined, in this case 
there are three options, "above", "below" and "horizontal-padding", defined as 
markups and number, with default values. Inside the function they are available 
through an alist with the name of props (which is admittedly incosistent and 
should be changed).

The function could for example be used like

\annotateCenteredMusic \with { above = "Hello" } { c'1 }
or even

\annotateCenteredMusic { c'1 }
(which would not make any sense in this specific case because the defaults are 
empty markups).

annotateCenteredMusic =
#(with-options define-music-function (music)(ly:music?)
   `(strict
     (? above ,markup? ,(markup #:null))
     (? below ,markup? ,(markup #:null))
     (? horizontal-padding ,number? 2)
     )
   ;; Store data in a closure to drag it over from the music-function stage
   ;; to before-line-breaking and stencil
   (let ((upper
          ;; apply \Xg (ascender/descender placeholder) for non-null markups
          ;; NOTE:
          ;; in multiline markups the \Xg markup command has to be applied
          ;; manually at one point in the last line if that last line
          ;; doesn't contain a descender!
          (let ((original (assq-ref props 'above)))
            (if (equal? original (markup #:null))
                original
                (markup #:Xg original))))
         (lower
          (let ((original (assq-ref props 'below)))
            (if (equal? original (markup #:null))
                original
                (markup #:Xg original))))
         (music-stil #f)
         (upper-stil #f)
         (lower-stil #f)
         (upper-padding (getOption '(mozart centered staff-padding-up)))
         (lower-padding (getOption '(mozart centered staff-padding-down)))
         (horizontal-padding (assq-ref props 'horizontal-padding)))
     #{
       \tweak before-line-breaking
       #(lambda (grob)
          ;; Create the three markup stencils *now* and store it in the closure
          ;; so we can use its dimensions to affect the layout.
          (set! music-stil #{ \getBareScoreMarkupStencil #grob #music #})
          (set! upper-stil (grob-interpret-markup grob upper))
          (set! lower-stil (grob-interpret-markup grob lower))
          (let*
           ((max-text-width
             (max
              (interval-length (ly:stencil-extent upper-stil X))
              (interval-length (ly:stencil-extent lower-stil X))))
            (music-width (interval-length (ly:stencil-extent music-stil X)))
            (width-diff
             (abs (/ (- max-text-width music-width) 2)))
            (dummy (ly:message "Music width: ~a" width-diff))
            )

          (ly:grob-set-property! grob 'Y-extent
            ;; Include the markups in the Y-extent of the MMR
            ;; so it won't get cut off the page
            (cons
             (min
              (- 0 2 lower-padding (interval-length (ly:stencil-extent 
lower-stil Y)))
              (car (ly:stencil-extent music-stil Y)))
             (max
              (+ 2 upper-padding (interval-length (ly:stencil-extent upper-stil 
Y)))
              (cdr (ly:stencil-extent music-stil Y)))
              ))

          (ly:grob-set-property! grob 'minimum-length
            ;; widen the measure to encompass music content, upper, and lower 
markup
            ; TODO: This still is confused by leading Clef/Time/Key
            (+ horizontal-padding
              (max
               (interval-length (ly:stencil-extent upper-stil X))
               (interval-length (ly:stencil-extent lower-stil X))
               (interval-length (ly:stencil-extent music-stil X)))))
          ))
       \tweak stencil
       #(lambda (grob)
          ;; Replace the MMR stencil with the combined stencil created earlier
          (annotate-centered grob music-stil
            upper-padding upper lower-padding lower))
       R1
     #}))

Urs



reply via email to

[Prev in Thread] Current Thread [Next in Thread]