2017-09-03 18:30 GMT+02:00 Thomas Morley <thomasmorle...@gmail.com>:

> I think I know how to proceed,

@Kieren
Attached the newest and heavily revised version.
Please read comments for usage.

@David
For one example I use predefined markup-commands like
  \markup with-red = \markup \with-color #red \etc

I seem to remember there was some even simpler possibility. Or was it
just a proposal? I can't find it at the moment.

Btw,
\markup my-concat = \markup \concat { \etc "!" }
\markup \my-concat "foo"
fails, no surprise, just a dream ...

Cheers,
  Harm
\version "2.19.64"

#(use-modules (ice-9 regex))

#(define (note-column::main-extent grob)
"Return extent of the noteheads in the 'main column', (i.e. excluding any 
suspended noteheads), or extent of the rest (if there are no heads)."
  (let* ((note-heads (ly:grob-object grob 'note-heads))
         (stem (ly:grob-object grob 'stem))
         (rest (ly:grob-object grob 'rest)))
    (cond ((ly:grob-array? note-heads)
           (let (;; get the cdr from all note-heads-extents, where the car 
                 ;; is zero
                 (n-h-right-coords
                   (filter-map
                     (lambda (n-h)
                       (let ((ext (ly:grob-extent n-h grob X)))
                          (and (= (car ext) 0) (cdr ext))))
                     (ly:grob-array->list note-heads))))
             ;; better be paranoid, find the max of n-h-right-coords and return
             ;; a pair with (cons 0 <max>)
             (cons 0.0 (reduce max 0 n-h-right-coords))))
          ((ly:grob? rest)
           (ly:grob-extent rest grob X))
          ;; better be paranoid
          (else '(0 . 0)))))

%% TODO #\space as well?
#(define char-set:dynamics
  (char-set #\f #\m #\p #\r #\s #\z)) 

%% TODO
%% There's the scheme-procedure `make-regexp', I'm not confident with reg-exps 
%% to use it, though
#(define (make-reg-exp separator-pair)
  (format #f "\\~a[^~a~a]*\\~a"
    (car separator-pair)
    (car separator-pair)
    (cdr separator-pair)
    (cdr separator-pair)))
    
#(define (dynamics-list separator-pair strg)
;; Takes a string, which is splitted at space. Local reg-exp and separators are
;; processed from @var{separator-pair}.
;; Dynamic signs within the splitted string (which are rendered by separators)  
;; are selected by matching reg-exp and by containing only dynamic characters 
;; between the separators.  
;;
;; Returns a new list containing not-dynamic strings and sublists with always
;; three entries.  Before-the-dynamic - dynamic - after-dynamic.
;; 
;; Example:
;; (dynamics-list (cons #\{ #\}) "poco -{f}- piu"))
;; =>
;; (list "poco" (list "-" "f" "-") "piu")
;; 
  (let ((reg-exp (make-reg-exp separator-pair))
        (separators (char-set (car separator-pair) (cdr separator-pair))))
    (map
      (lambda (s)
        (let* ((match (string-match reg-exp s)))
           (if match 
               (let* ((poss-dyn (match:substring match))
                      (cand (string-trim-both poss-dyn separators)))
                 (if (string-every char-set:dynamics cand)
                       (list
                         (match:prefix match)
                         cand
                         (match:suffix match))
                     s))  
               s)))
      (string-split strg #\space))))
      
#(define (get-all-list-indices lst)
"Takes a list and returns a new list of all indices of sublists in @var{lst}"
  (filter-map
    (lambda (e c) (if (list? e) c #f))
    lst
    (iota (length lst))))

#(define (dynamic-text::format-dynamics fontsize markup-command lst)
;; (1) Convert lst into a list where the targeted string is rendered
;;     with dynamic-markup.  The targeted string is identified by being
;;     second in a three-element-(sub-)list of lst.
;; (2) remove empty strings from (sub-)lists.
;; (3) insert " " between any element of lst but not between
;;     elements of the (sub-)lists
;; (4) Return a new list, unfolded one level
;; TODO disentangle applying markup-commands from other stuff?
    (append-map
      (lambda (y) (if (list? y) y (list y)))
      (list-insert-separator
        (map
          (lambda (e)
            (if (and (list? e) (= (length e) 3))
                (remove
                   (lambda (x) (and (string? x) (string-null? x)))
                   (list
                     (car e)
                     (if (number? fontsize)
                         (make-fontsize-markup fontsize
                           (markup-command (second e)))
                         (markup-command (second e)))
                     (last e)))
                e))
          lst)
        " ")))
        
#(define (dynamic-text::format-text fontsize markup-command lst)
"Format string-parts of @var{lst} with @var{fontsize} and @var{markup-command}"
  (map
    (lambda (arg)
      (if (string? arg)
          (if (number? fontsize)
              (make-fontsize-markup fontsize
                (markup-command arg))
              (markup-command arg))
          arg))
    lst))
           
#(define (get-list-parts lst dyn-indices idx)
;; Relying on @var{idx}, which selects from @var{dyn-indices} return a new
;; list containing sublists with stuff before the selected dynamic, the
;; dynamic itself and stuff after the dynamic.
  (if (null? dyn-indices)
      (list '() '() '())
      (let* (;; if idx exceeds, print a warning and use first possible
             ;; dynamic
             (dyn-pos 
               (if (>= idx (length dyn-indices))
                   (begin
                     (ly:warning 
                       "requested dynamic to align does not exist, ignoring")
                     (car dyn-indices))
                   (list-ref dyn-indices idx)))
             (before-dyn (take lst dyn-pos))
             (dyn-to-align (list-ref lst dyn-pos))
             (after-dyn (drop lst (1+ dyn-pos))))
        (list
          before-dyn
          dyn-to-align
          after-dyn))))
  
dynamicH =
#(define-event-function (align-on-dyn? idx strg) 
  ((boolean? #f)(index? 1) string?)
;; Takes a string, puts out a formated dynamic-script using a certain 
;; markup-command for identified DynamicText, and another markup-command for all 
;; other stuff.
;; Both markup-commands are called from 'details.markup-commands. If not set
;; make-dynamic-markup and make-italic-markup are used.
;; Font-sizes for both are called from 'details.dyn-rest-font-sizes. If not set
;; default is used.
;; This text is placed below the NoteColumn, with first occurring DynamicText
;; centered. 
;;
;; Setting the optional @var{idx} makes it possible to choose other
;; occurring DynamicText.
;; If some other text is before the DynamicText it will be printed left
;; aligned.  This may be changed by setting optional @var{align-on-dyn}.
;;
;; Be aware while using any optional variable you need to set both.
;; 
;; The appearance is futher tweakable by applying tweaks for self-alignment-X
;; and X-offset.
;; If using a tweak for self-alignment-X the calculated value for X-offset will 
;; not be used.
;; If using a tweak for X-offset, this value will be added to the calculated 
;; one.
;; 
;; Limitations: 
;;   - Does not respond to _overrides_ of self-alignment-X

  (let* ((dynamic (make-music 'AbsoluteDynamicEvent))
         (tweak-proc
           (lambda (grob)
             (let* (;; get the fontsizes to use from the relevant 
                    ;; details-sub-property, i.e. 'dyn-rest-font-sizes
                    (dyn-rest-font-sizes 
                      (assoc-get 
                        'dyn-rest-font-sizes 
                        (ly:grob-property grob 'details)
                        (cons #f #f)))
                    ;; get the markup-commands to use from the relevant 
                    ;; details-sub-property, i.e. 'markup-commands
                    (markup-commands 
                      (assoc-get 
                        'markup-commands 
                        (ly:grob-property grob 'details)
                        (cons make-dynamic-markup make-italic-markup)))
                    (separator-pair
                      (assoc-get 
                        'separator-pair 
                        (ly:grob-property grob 'details)
                        (cons #\{ #\})))
                    ;; get a nested list with dynamics in sublists
                    (basic-dyn-list (dynamics-list separator-pair strg))
                    ;; do dynamic-markups, remove empty strings
                    (cleaned-basic-dyn-list 
                      (dynamic-text::format-dynamics 
                          (car dyn-rest-font-sizes) 
                          (car markup-commands) 
                          basic-dyn-list))
                    ;; get indices of dynamics
                    (all-dyn-indices 
                      (get-all-list-indices cleaned-basic-dyn-list))
                    ;; do other text-markups
                    (text-dyn-mrkp-list 
                      (dynamic-text::format-text 
                        (cdr dyn-rest-font-sizes)
                        (cdr markup-commands)
                        cleaned-basic-dyn-list))
                    ;; get a list containing: 
                    ;;   before-dynamic, dynamic, after-dynamic
                    ;; list-ref starts with zero for the first element, thus 
                    ;; use (1- idx) for a nicer user-interface
                    (splitted-text-dyn-mrkp-list 
                      (get-list-parts 
                        text-dyn-mrkp-list all-dyn-indices (1- idx)))
                    (all-markups
                      (map
                        (lambda (e)
                          (if (markup-list? e)
                              (make-normal-text-markup
                                (make-concat-markup e))
                              e))
                        splitted-text-dyn-mrkp-list))
                    (all-stils
                      (map
                        (lambda (mrkp)
                          (grob-interpret-markup grob mrkp))
                        all-markups))
                    (layout (ly:grob-layout grob))
                    (line-thick (ly:output-def-lookup layout 'line-thickness))
                    (all-stil-lengths
                      (map
                        (lambda (stil)
                          (let* ((stil-ext (ly:stencil-extent stil X))
                                 (left-car (if (interval-sane? stil-ext) 
                                           (car stil-ext)
                                           0))
                                 ;; if the markup-command used to render 
                                 ;; dynamics, causes negative extent to the left
                                 ;; and the entire dynamic expression starts
                                 ;; with an empty stencil, it's needed to add
                                 ;; some calculated correction
                                 (corr
                                   (+ (* 2 left-car)
                                   (/ line-thick 2))))
                            (+ 
                              (interval-length stil-ext) 
                              (if (ly:stencil-empty? (car all-stils))
                                  corr
                                  0))))
                        all-stils))
                    (calculated-x-off
                      (if (markup? (second all-markups))
                          (let* ((x-par (ly:grob-parent grob X))
                                 (parent-x-ext-center 
                                   (interval-center 
                                     (if (ly:grob-property grob 
                                           'X-align-on-main-noteheads)
                                         (note-column::main-extent x-par)
                                         (ly:grob-extent x-par x-par X)))))
                            ;; The final calculation takes the extent of the 
                            ;; NoteColumn into account.
                            ;; If there is some other text before the dynamic, 
                            ;; return 0, but not if align-on-dyn is #t
                            (if (or (zero? (car all-stil-lengths)) 
                                    align-on-dyn?)
                                (- parent-x-ext-center
                                   (car all-stil-lengths)
                                   (/ (second all-stil-lengths) 2)
                                   )
                                0))
                          ;; if no dynamic at all, do (my choice):
                          0))
                    ;; get tweaks for self-alignment-X
                    (prev-self-alignment-X-tweaks
                      (filter
                        (lambda (tw)
                          (eq? (car tw) 'self-alignment-X))
                        (ly:prob-property 
                          (ly:grob-property grob 'cause) 
                          'tweaks)))
                    ;; Get previous tweaks for X-offset and add their values
                    ;; They are added to the final result  
                    (prev-x-offset-tweaks
                      (filter
                        (lambda (tw)
                          (and (number? (cdr tw)) (eq? (car tw) 'X-offset)))
                        (ly:prob-property 
                          (ly:grob-property grob 'cause) 
                          'tweaks)))
                    (prev-x-off (apply + (map cdr prev-x-offset-tweaks))))

             ;; TODO is it safe to put the stencil-creation into 
             ;;      'before-line-breaking?
             (begin
               (ly:grob-set-property! grob 'stencil
                (stack-stencils X RIGHT 0 all-stils))
               ;; if previous tweaks for self-alignment-X are present return '()
               (if (not (pair? prev-self-alignment-X-tweaks))
                   (ly:grob-set-property! grob
                     'X-offset (+ prev-x-off calculated-x-off))
                   '()))))))
    ;; If a previous tweak for self-alignment-X is present, set 
    ;; 'before-line-breaking to the empty list retuned by x-off-proc for this
    ;; case.
    ;; Otherwise 'before-line-breaking will change 'X-offset to the calculated
    ;; value returned from x-off-proc (taking previous tweaks for 'X-offset
    ;; into account.
    ;; TODO need to keep previous settings of 'before-line-breaking?
    (set! (ly:music-property dynamic 'tweaks)
          (acons 'before-line-breaking
                 tweak-proc
                 (ly:music-property dynamic 'tweaks)))
    dynamic))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% REMARKS
%% All examples align the dynamic under the NoteColumn
%% Remove the optional arguments in 
%%    \dynamicH ##t 1 ...
%% if you want different behaviour
%%
%% Some examples use fonts Purisa and LilyJazz, make sure you have them

%#(set-default-paper-size "a4")

\paper { indent = 5 \cm  }

layoutDefault =
  \layout {
  	%% DynamicText may be customized with overrides as below.
  	%% Currently given are the defaults.
  	%
    %\override DynamicText.details.separator-pair = #(cons #\{ #\})
    %
    %% first value of the pair is used to determine fontsize of dynamics, second
    %% for other text
    %\override DynamicText.details.dyn-rest-font-sizes = #'(0 . 0)
    %
    %% first value of the pair is used to render dynamics, second for other text
    %% Be aware: if you change/extend the simple make-dynamic-markup, but
    %% want to have the dynamics _all_ rendered with dynamic-font wrap your new
    %% command around make-dynamic-markup
    %\override DynamicText.details.markup-commands = 
    %  #(cons make-dynamic-markup make-italic-markup)
    %
    %\override DynamicText.font-size = 0
  }
  
%% Change fontsize independently
layoutI =
  \layout {
  	  \override DynamicText.details.dyn-rest-font-sizes = #'(3 . -2)
  }

%% Change used markup-commands independently
layoutII =
  \layout {
  	\override DynamicText.details.dyn-rest-font-sizes = #'(5 . 0)
    \override DynamicText.details.markup-commands =
      #(cons
         (lambda (arg)
           (markup 
             #:normal-text 
             #:override '(box-padding . 0.5) 
             #:override '(thickness . 3) 
             #:box 
             #:bold 
             #:override '(font-name . "LilyJazz")
             arg))
         (lambda (arg) 
           (markup
             ;; Limitation:
             ;; underline returns a nice output by accident!
             ;; undertie not
             ;; Reason: every single part of the text markup needs to be 
             ;; processed separately, otherwise the offsetting calculation will
             ;; be broken
             #:underline
             #:override '(font-name . "Purisa")
             arg)))
  }
  
%% Change used markup-commands independently
%% Other syntax
layoutIII =
  \layout {
    \override DynamicText.details.markup-commands =
      #(cons 
         (lambda (arg)
           #{ 
           	   \markup 
           	     \override #'(padding . 0.7) 
           	     \override #'(thickness . 2.5) 
           	     %% REMARK to self:
           	     %% patch make-bracket-markup, its thickness is not customizable
           	     \parenthesize 
           	     \dynamic
           	     $arg 
           #})
         (lambda (arg) #{ \markup \bold $arg #}))
    \override DynamicText.font-size = 0
  }
  
%% Change used markup-commands independently
%% Again other syntax

\markup customize-dyn = 
  \markup \ellipse \dynamic \etc
  
\markup with-red = 
  \markup \with-color #red \etc

layoutIV =
  \layout {
    \override DynamicText.details.markup-commands =
      #(cons make-customize-dyn-markup make-with-red-markup)
    \override DynamicText.font-size = 0
  }
  
%% \dynamicH takes two optional arguments, see above.
%% As soon as more sophisticated markup-commands are used to render the dynamic
%% part using them is recommended.
%% For the sake of the examples shortness, they are always applied here
mus =
  <<
    \new Staff { c''1\dynamicH ##t 1 "text before {ppppp}" }

    \new Staff { c''1\dynamicH ##t 1 "{ppppp} text after" }

    \new Staff { c''1\dynamicH ##t 1 "text before {ppppp} text after" }
    %% helper for better viewing
    \addlyrics %\with { \override LyricText.parent-alignment-X = #LEFT }
      { \markup \with-dimensions #'(0 . 0) #'(0 . 0) \draw-line #'(0 . 30) }
  >>
  
\score { \mus \layoutDefault \header { piece = "DEFAULTS" } }

\score { \mus \layoutI \header { piece = "FONTSIZES" } }

\score { \mus \layoutII \header { piece = "MARKUP-COMMANDS" } }

\score { \mus \layoutIII \header { piece = "MARKUP-COMMANDS" } }

\score { \mus \layoutIV \header { piece = "MARKUP-COMMANDS" } }

\score { 
  \new Staff { c''1\dynamicH ##t 1 "text before _ppppp_ text after" }
  \layout {
    \override DynamicText.details.separator-pair = #(cons #\_ #\_)
  }
  \header { piece = "Changed SEPARATOR-PAIR (no visible changes)" } 	
}

\score { 
  \new Staff { c''1\dynamicH ##t 2 "center on {ppppp} second {ff} dynamic" }
  \layoutIV
  \header { piece = "CENTER ON SECOND DYNAMIC" } 	
}

_______________________________________________
lilypond-user mailing list
lilypond-user@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to