Am So., 22. März 2020 um 17:38 Uhr schrieb Leo Correia de Verdier
<[email protected]>:
>
> Second attempt:
[...]

Hi,

please find attached my own take on it.

The tremolo-beam now avoids left-note-column's dots and
right-note-column accidentals automagically and is
centered around the thought line between the vertical center of the
note-heads staff-positions of left and right note-column.
Furthermore, I introduced some subproperties of Beam.details:
(1)
Beam.details.tremolo-positions, expecting a pair, defaulting to '(0 . 0)
for fine-tuning
NB Introducing details.tremolo-positions is part of the workaround. A
user set 'positions would disturb the stencil-examination. If you use
Beam.positions, you will need to figure out all the needed values.
Then the function assumes you want to Y-position the Beam yourself and
restricts it's behaviour to left/right gaps.
(2)
Beam.details.whole-note-tremolo-gaps, expecting a pair, defaulting to
'(0.5 . 0.5)
for fine-tuning the gaps

Note:
because of
https://sourceforge.net/p/testlilyissues/issues/5868/
an additional function 'correct-whole-note-tremolo-gap' is added.

In general, the function examines the default-stencil and returns a
modified one.
The first ~90 lines are debugging helpers. They may be activated
setting the boolean in line 369 to #f
Ofcourse it would be better the default would do the right thing right
from the begining...

Thus, it's a workaround, nothing more, but demonstrating how tremolo
for whole notes should come out per default imho.


Cheers,
  Harm
\version "2.20.0"

%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% debugging helpers
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(define* (make-cross-stencil coords #:optional (thick 0.2) (sz 0.3))
 (ly:stencil-add
   (make-line-stencil
     thick
     (- (car coords) sz)
     (- (cdr coords) sz)
     (+ (car coords) sz)
     (+ (cdr coords) sz))
   (make-line-stencil
     thick
     (- (car coords) sz)
     (+ (cdr coords) sz)
     (+ (car coords) sz)
     (- (cdr coords) sz))))

#(define (beam-stencil stil)
"Add some visual debugging-aid to the given @var{stil}, which is supposed to be
a beam-stencil."
  (lambda (grob)
    (let* ((sys (ly:grob-system grob))
           (left-stem (ly:spanner-bound grob LEFT))
           (right-stem (ly:spanner-bound grob RIGHT))
           (left-stem-x (ly:grob-relative-coordinate left-stem sys X))
           (right-stem-x (ly:grob-relative-coordinate right-stem sys X))
           ;; left/right NoteColumn
           (left-nc (ly:grob-parent left-stem X))
           (right-nc (ly:grob-parent right-stem X))
           ;; left NoteHeads average staff-positions
           (left-y-average 
             (get-average-staff-positions-of-note-heads left-nc))
           ;; right NoteHeads average staff-positions
           (right-y-average 
             (get-average-staff-positions-of-note-heads right-nc))
           ;(stil (ly:beam::print grob))
           (details (ly:grob-property grob 'details))
           (tremolo-positions
             (assoc-get 'tremolo-positions details '(0 . 0)))
           (beam-dir
             (if (> (ly:grob-property grob 'direction) 0)
                 "UP"
                 "DOWN"))
           (stil-x-ext (ly:stencil-extent stil X))
           (stil-start (car stil-x-ext))
           (stil-end (cdr stil-x-ext))
           (stil-y-ext (ly:stencil-extent stil Y))
           (stil-y-center (interval-center stil-y-ext)))
      
    (ly:stencil-add
      stil
      ;; Print the Beam.direction
      (ly:stencil-translate-axis (grob-interpret-markup grob beam-dir) 4 Y)
      ;; Print a cross at point (left-stem-x-coord left-y-average-coord)
      ;; division by 2 to reflect half-staff-space unit, below as well
      (make-cross-stencil 
        (cons 0 (+ (/ left-y-average 2))))
      ;; Print a cross at point (right-stem-x-coord right-y-average-coord)
      (make-cross-stencil 
        (cons (- right-stem-x left-stem-x) (+ (/ right-y-average 2))))
      ;; print a blue line connecting left/right-y-average 
      (stencil-with-color
        (make-line-stencil 
          0.1 
          0 (/ left-y-average 2) 
          (- right-stem-x left-stem-x) (/ right-y-average 2))
        blue)
      ;; print two vertical lines at left/right edge of the given stil
      (make-line-stencil 
        0.1 
        stil-start (- stil-y-center 5) 
        stil-start (+ stil-y-center 5))
      (make-line-stencil 
        0.1 
        stil-end (- stil-y-center 5) 
        stil-end (+ stil-y-center 5))))))
        
#(define whole-note-stem-stencil
  (lambda (grob) 
  "Debugging-aid, print a simple vertical line for whole-note stems."
    (if (zero? (ly:grob-property grob 'duration-log))
        (make-line-stencil 0.1 0 -5 0 5)
        ly:stem::print)))
        
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% the code
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#(define (lists-map function ls)
"Apply @var{function} to @var{ls} and all of it sublists.
First it recurses over the children, then the function is applied to
@var{ls}."
    (if (list? ls)
        (set! ls (map (lambda (y) (lists-map function y)) ls))
        ls)
    (function ls)) 
    
#(define (get-average-staff-positions-of-note-heads note-column)
;; The names says it all
  (let* ((note-heads-array (ly:grob-object note-column 'note-heads)))
    (if (ly:grob-array? note-heads-array)
        (let* ((note-heads-list (ly:grob-array->list note-heads-array))
               (note-heads-staff-positions
                 (map 
                   (lambda (nh) (ly:grob-property nh 'staff-position nh)) 
                   note-heads-list)))
          (/ (apply + note-heads-staff-positions) 
             (length note-heads-staff-positions)))
        #f)))
    
#(define (reset-polygon vals blot)
  (lambda (l)
  "If @var{l} is a list defining a polygon-stencil, return a new list defining
a polygon-stencil with modified coords, relying on the given @var{vals}.
Take @var{blot} into account."
    (if (and (list? l) (eq? (car l) 'polygon))
        (let* ((coords (primitive-eval (cadr l)))
               (x-right (assoc-get 'x-right vals))
               (x-left (assoc-get 'x-left vals))
               (y-left (assoc-get 'y-left vals))
               (y-right (assoc-get 'y-right vals))
               (new-lst
                 (list
                   (- (car coords) x-right) (+ y-right (second coords))
                   (- (third coords) x-right) (+ y-right (fourth coords))
                   (+ (fifth coords) x-left) (+ y-left (sixth coords))
                   (+ (seventh coords) x-left) (+ y-left (eighth coords)))))
           `(polygon ',new-lst ,blot #t))
        l)))
      
#(define whole-note-tremolo-beam?
  (lambda (grob)
  "Predicate: Is current beam applied to whole notes as a tremolo-beam?"
    (let* ((stems
             (map (lambda (dir) (ly:spanner-bound grob dir)) '(-1 1)))
           (stem-durs
             (map (lambda (stem) (ly:grob-property stem 'duration-log)) stems))
           (beam-cause (ly:grob-property grob 'cause))
           (tremolo?
             (and (ly:prob? beam-cause)
                  (member 
                    'tremolo-span-event 
                    (ly:prob-property beam-cause 'class)))))

      (and tremolo? (any zero? stem-durs)))))
      
%% The function tries to improve whole-note tremolo
%%
%%  Current situation:
%%    - Whole-note tremolo-beams are always horizontal
%%    - Dots and accidentals are not avoided
%%    - 'gap is buggy, see issue 5868
%%         https://sourceforge.net/p/testlilyissues/issues/5868/
%%
%%  Correction
%%    - Let the beam point from the average staff-position of left note-columns 
%%      note-heads to the average staff-position of right note-columns 
%%      note-heads.
%%      Customizable by override for Beam.details.tremolo-positions
%%      defaulting to '(0 . 0)
%%    - Avoid dots and accidentals per default, by calculating accurate gaps.
%%      Customizable by override for Beam.details.whole-note-tremolo-gaps 
%%      defaulting to '(0.5 . 0.5)
%%    - Relies on zero-gap. To give the user the possibility to set Beam.gap
%%      for non-whole notes, a separate procedure is given, see below.
#(define whole-note-tremolo-correction
  (lambda (grob)
    (if (whole-note-tremolo-beam? grob)
        (let* ((details (ly:grob-property grob 'details))
               (whole-note-tremolo-gaps
                 (assoc-get 'whole-note-tremolo-gaps details '(0.5 . 0.5)))
               (left-gap (car whole-note-tremolo-gaps))
               (right-gap (cdr whole-note-tremolo-gaps))
               (sys (ly:grob-system grob))
               (grob-layout (ly:grob-layout grob))
               ;; Beam
               ;; TODO where does this magic number, 0.81, comes from?
               (length-fraction (ly:grob-property grob 'length-fraction 0.81))
               (beam-dir (ly:grob-property grob 'direction))
               ;; 0.48 taken from IR
               (beam-thick (ly:grob-property grob 'beam-thickness 0.48))
               (beam-count (length (ly:grob-property grob 'beam-segments)))
               ;; beam-height at arbitrary x
               (beam-y (+ beam-thick (* (1- beam-count) length-fraction)))
               (stil (ly:beam::print grob))
               (stil-expr (ly:stencil-expr stil))
               (stil-x (ly:stencil-extent stil X))
               (stil-x-length (interval-length stil-x))
               (stil-y (ly:stencil-extent stil Y))
               (blot (ly:output-def-lookup grob-layout 'blot-diameter 0))
               (line-thick 
                 (ly:output-def-lookup grob-layout 'line-thickness 0.1))
               ;;;;;;;;;;;;;;;;;
               ;;;; left side
               ;;;;;;;;;;;;;;;;;
               ;; Stem
               (left-stem (ly:spanner-bound grob LEFT))
               (left-stem-x (ly:grob-relative-coordinate left-stem sys X))
               ;; NoteColumn
               (left-nc (ly:grob-parent left-stem X))
               (left-nc-x-ext::right (cdr (ly:grob-extent left-nc sys X)))
               ;; left NoteHeads average staff-positions
               (left-y-average 
                 (get-average-staff-positions-of-note-heads left-nc))
               ;; DotColumn
               (left-dot-column (ly:note-column-dot-column left-nc))
               (left-dot-column-x-ext 
                 (if (ly:grob? left-dot-column)
                     (ly:grob-extent left-dot-column sys X)
                     #f))
               (left-dot-column-right-x::right
                 (if (and left-dot-column-x-ext
                          (interval-sane? left-dot-column-x-ext))
                     (cdr left-dot-column-x-ext)
                     ;; if no dots present, fall back to left-nc-x-ext::right
                     left-nc-x-ext::right))
               ;;;;;;;;;;;;;;;;;
               ;;;; right side
               ;;;;;;;;;;;;;;;;; 
               ;; Stem
               (right-stem (ly:spanner-bound grob RIGHT))
               (right-stem-x (ly:grob-relative-coordinate right-stem sys X))
               ;; NoteColumn
               (right-nc (ly:grob-parent right-stem X))
               (right-nc-x-ext::left (car (ly:grob-extent right-nc sys X)))
               ;; right NoteHeads average staff-positions
               (right-y-average 
                 (get-average-staff-positions-of-note-heads right-nc))
               ;; AccidentalPlacement
               (right-acc-placement (ly:note-column-accidentals right-nc))
               (right-acc-placement-x-ext 
                 (if (ly:grob? right-acc-placement)
                     (ly:grob-extent right-acc-placement sys X)
                     #f))
               (right-acc-x-ext::left
                 (if (and right-acc-placement-x-ext
                          (interval-sane? right-acc-placement-x-ext))
                     (car right-acc-placement-x-ext)
                     ;; if no accidentals present, fall back to 
                     ;; right-nc-x-ext::left 
                     right-nc-x-ext::left))
               ;; calculate left/right gaps
               (calculated-left-gap
                 (- left-dot-column-right-x::right left-nc-x-ext::right))
               (calculated-right-gap
                 (- right-nc-x-ext::left right-acc-x-ext::left))
               (raw-calculated-stil-width
                 (- stil-x-length
                    calculated-left-gap calculated-right-gap))
               (added-gaps
                 (+ left-gap 
                    right-gap 
                    calculated-left-gap 
                    calculated-right-gap))
               (compensate-left
                 (if (> added-gaps stil-x-length)
                  (begin
                    (ly:warning 
                  "specified gap exceeds stencil-width by ~a, setting gap zero."
                      (- (+ left-gap right-gap) raw-calculated-stil-width))
                    calculated-left-gap)
                  (+ calculated-left-gap left-gap)))
               (compensate-right
                 (if (> added-gaps stil-x-length)
                  (begin
                    (ly:warning 
                  "specified gap exceeds stencil-width by ~a, setting gap zero."
                      (- (+ left-gap right-gap) raw-calculated-stil-width))
                    calculated-right-gap)
                  (+ calculated-right-gap right-gap)))
               ;; calculate gradient
               (gradient
                 (/
                   (/ (- right-y-average left-y-average) 2)
                   (- right-stem-x left-stem-x)))
               ;; If a user overrides 'positions, we let him do so, i.e. no
               ;; further vertical adjustment is done.
               ;; To fine-tune left/right vertical positions of the Beam we look 
               ;; instead at Beam.details.tremolo-positions, 
               ;; defaulting to '(0 . 0).
               (positions-default
                 (beam::place-broken-parts-individually grob))
               (positions-prop
                 (ly:grob-property grob 'positions))
               (user-defined-positions?
                 ;; TODO sufficient?
                 (and (number-pair? positions-default)
                      (number-pair? positions-prop)
                      (or (not (= (car positions-default) 
                               (car positions-prop)))
                          (not (= (cdr positions-default) 
                               (cdr positions-prop))))))
               (tremolo-positions
                 (assoc-get 'tremolo-positions details '(0 . 0)))
               (y-start 
                 (if user-defined-positions? 
                     0
                     (car tremolo-positions)))
               (y-end
                 (if user-defined-positions?
                     0
                     (+
                        (cdr tremolo-positions)
                        (* gradient 
                           (- stil-x-length 
                              blot 
                              compensate-right 
                              compensate-left)))))
               (vals
                 (list
                   (cons 'x-right compensate-right)
                   (cons 'x-left compensate-left)
                   (cons 'y-left y-start) 
                   (cons 'y-right y-end)))
               (new-x-ext
                 (cons 
                   (+ (car stil-x) compensate-left) 
                   (- (cdr stil-x) compensate-right)))
               (new-y-ext
                 (if user-defined-positions? 
                     stil-y
                     (cons 
                       (+ (car stil-y) (min y-start y-end))
                       (+ (cdr stil-y) (max y-start y-end)))))
               (y-translation-value
                 ;; If the user sets 'positions, we assume he cares himself 
                 ;; about positioning, thus don't move the stencil in 
                 ;; Y-direction
                 (if user-defined-positions?
                     0
                     ;; We introduce a let here to let the user follow 
                     ;; the calculation step-by-step
                     (let* (;; Move the beam to zero, the outer edge of the 
                            ;; main-beam will match the zero-line of StaffSymbol
                            ;; Bext to follow with default beam.
                            (zero-line-main-beam
                              (+ (- (car positions-default)) 
                                 (* -1 beam-dir (/ beam-thick 2))))
                            ;; Move the beam to the calculated left-y-average
                            (main-beam-at-left-average
                              (+ zero-line-main-beam
                                 (/ left-y-average 2)))
                            ;; Move the beam to the (thought) line connecting 
                            ;; left- and right-y-average
                            ;; Bext to follow with the new beam
                            (main-beam-at-left-to-right-line
                              (+ main-beam-at-left-average
                                 (* (+ (car stil-x) compensate-left) gradient)))
                            ;; Move the beam in order to have it centered on 
                            ;; said line.  Some correction for line-thickness 
                            ;; is needed
                            (beam-centered-at-left-to-right-line
                              (+ main-beam-at-left-to-right-line 
                                 (* (/ line-thick 2) gradient)
                                 (* beam-dir (/ beam-y 2)))))
                       beam-centered-at-left-to-right-line)))
               (new-stencil  
                 (ly:stencil-translate-axis
                   (ly:make-stencil
                     (lists-map (reset-polygon vals blot) stil-expr)
                     new-x-ext
                     new-y-ext)
                   y-translation-value
                   Y)))
          ;; For #f some debugging output is added 
          (if #t
              new-stencil
              ;; add and print debugging output 
              (beam-stencil
                ;; visualize new-stencil-extents
                (box-stencil new-stencil 0 0)
                ;; commented default, if switched from above to it, usefull for 
                ;; debugging y-translation-value
                ;stil
                  )))
        ;; If beam is not a whole-note-tremolo-beam return default
        ly:beam::print)))
            
#(define* (correct-whole-note-tremolo-gap #:optional (val #f))
" Return zero for whole-note tremolos."
  (lambda (grob)
      (if (whole-note-tremolo-beam? grob)
          0
          (or val 0.8))))

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\paper { 
  ragged-right = ##f
  indent = 0 
}

\layout { 
  %\omit Staff.TimeSignature 
  \context { 
     \Voice
     \override Beam.gap = #(correct-whole-note-tremolo-gap)
     \override Beam.stencil = #whole-note-tremolo-correction
     %% other debugging-helper
     %\override Stem.stencil = #whole-note-stem-stencil
     %\override Beam.layer = 500
     %\override Beam.color = #red
  }
  
}

\transpose c c
{
  %% Some overrides for checking the behaviour:
  %\override Beam.gap = #(correct-whole-note-tremolo-gap 5)
  %\override Beam.details.whole-note-tremolo-gaps = #'(0.2 . 0.8)
  %\override Beam.positions = #'(0 . 0)
  %\override Beam.details.tremolo-positions = #'(1 . -1)
  
  \repeat unfold 2 {
    \repeat tremolo  #8 { a'16 <b''> }
    \repeat tremolo 4 { <g' d'' g''>8 \transpose c c'' <bes des' ees'> }
    \repeat tremolo 16 {
      <g' d'' g''>32 
      \transpose c c'' { <aes  bes ces' ees' ges'> }
    }
    \repeat tremolo 16 { <g' d''! g''>32 <bes des' ees'> }
    \time 6/4
    \repeat tremolo  #24 { a'32 b }
    \break
    \time 4/4
  }
  \time 4/4
  \once\override Beam.gap-count = 2
  \repeat tremolo 8 { a'32 ais'32 }
  b8 8 8 8
  4 4 4 4
  2 2 1 \breve
}


Reply via email to