Hi Kieren,

2015-12-01 21:18 GMT+01:00 Thomas Morley <thomasmorle...@gmail.com>:
[...]
> Continuing coding/thinking,
>   Harm

here the last version. Several comments inline.
The syllables are compared via the length of their stencil-extents.
Ofcourse this has disadvantages as said before and even some more.

However, please test, to tired to continue...

Cheers,
  Harm
\version "2.19.32"

%%%%%%%%%%%%%%%%%%%%%%%%%
%% CUSTOM-GROB-PROPERTY
%%%%%%%%%%%%%%%%%%%%%%%%%

#(define (define-grob-property symbol type? description)
  (if (not (equal? (object-property symbol 'backend-doc) #f))
      (ly:error (_ "symbol ~S redefined") symbol))

  (set-object-property! symbol 'backend-type? type?)
  (set-object-property! symbol 'backend-doc description)
  symbol)
  
#(define my-custom-grob-properties
  `(
    (align-to-melisma ,boolean? "Should LyricText be aligned to other LyricText
being in melisma?")
   ))
   
#(define (acknowledge-my-grob-properties lst)
  (for-each (lambda (x) (apply define-grob-property x)) lst))
    
#(acknowledge-my-grob-properties my-custom-grob-properties)

%%%%%%%%%%%%%%%%%%%%%%%%%
%% ENGRAVER
%%%%%%%%%%%%%%%%%%%%%%%%%

#(define (align-to-melisma-engraver ctx)
"
To be put in Score-context.
Collects all Voices, gets knowledge whether a melisma is active.
If a melisma is active, every selected LyricText's `self-alignment-X' is set to 
the value of `lyricMelismaAlignment' unless `align-to-melisma' is set #f.
Selection is done comparing the stencil-lengths.
"
   (let ((voices '())
         ;; currently not needed:
         ;(lyrics '())
         (lyric-texts '())
         (melisma? #f))
     `(
       (acknowledgers
         (lyric-syllable-interface
          . ,(lambda (engraver grob source-engraver)
               (let* (;; get `lyricMelismaAlignment', this value is used later
                      ;; to set all selected grob's 'self-alignment-X to it
                      (lyric-melisma-alignment 
                        (ly:context-property ctx 'lyricMelismaAlignment))
                      ;; `align-to-melisma?' may be set false, for manual
                      ;; settings 
                      (align-to-melisma? 
                        (ly:grob-property grob 'align-to-melisma #t))
;;;; here the problem starts
;;;; a bunch of stencils is created just to compare their lengths to the actual 
;;;; one, then thrown away :((
;;;; TODO: how to do it different?
;;;;       comparing strings, will fail for markups, markup->string is not 
;;;;       sufficient.
;;;;       But, comparing the stencil-length, as done here, will probably 
;;;;       return false-true for some cases.
;;;;      
;;;;       However, manual inserting other values for`self-alignment-X' is 
;;;;       still possible in combination with `align-to-melisma' set #f
                      (lyric-texts-stencil-x-lengths
                        (sort
                          (map
                            (lambda (e)
                              (interval-length 
                                (ly:stencil-extent 
                                  (grob-interpret-markup grob e) 
                                  X)))
                            lyric-texts)
                            <))
                      (actual-stencil-length
                        (interval-length 
                          (ly:stencil-extent 
                            (ly:grob-property grob 'stencil) 
                            X)))
                      ;; align, when the actual stencil-length is present
                      ;; more than once in `lyric-texts-stencil-x-lengths'
                      ;; TODO find a less clumsy method
                      (should-be-aligned
                        (let ((sub-lst
                                (member 
                                  actual-stencil-length 
                                  lyric-texts-stencil-x-lengths)))
                          (and (not (null? sub-lst))
                               (not (null? (cdr sub-lst)))
                               (= (car sub-lst) (cadr sub-lst))))))
                               
                 ;; align selected grobs
                 (if (and melisma? align-to-melisma? should-be-aligned)
                     (ly:grob-set-property! 
                       grob 'self-alignment-X lyric-melisma-alignment))))))
                  
       (listeners
         ;; get all syllables at current time-step, so `acknowledgers' can work
         ;; on the complete(!) list, which is called `lyric-texts'
         ;; `lyric-texts' will be cleared before moving on to next time-step
         ;; see `stop-translation-timestep'
         (lyric-event
           .
           ,(lambda (engraver event)
              (let ((syllable-text (ly:event-property event 'text)))
                (set! lyric-texts (cons syllable-text lyric-texts)))))
         ;; get all Voices:
         ;; collect all contexts, filter for Voices, 
         ;;    (re-)set local variable `voices'
         ;;
         ;; TODO: `RemoveContext' may be important
         ;;       Test creating/stopping new Voices at different time-steps
         (AnnounceNewContext
           .
           ,(lambda (engraver event)
              (let ((context (ly:event-property event 'context)))
               ;; currently not needed:
               ;(if (eq? (ly:context-name context) 'Lyrics)
               ;    (set! lyrics (cons context lyrics)))
               (if (eq? (ly:context-name context) 'Voice)
                   (set! voices (cons context voices)))))))
                  
       (process-music
        . ,(lambda (trans)
             ;; Get knowledge whether a melisma starts at current time-step.
             ;; Hence look, if one element of `melismaBusyProperties' returns #t
             ;; default for `melismaBusyProperties' is:
             ;;   (list 'melismaBusy
             ;;         'slurMelismaBusy
             ;;         'tieMelismaBusy
             ;;         'beamMelismaBusy
             ;;         'completionBusy)
             (let ((melisma-props 
                     (ly:context-property ctx 'melismaBusyProperties)))
               (for-each
                 (lambda (voice)
                   (for-each 
                     (lambda (prop)
                       (let ((mlsm (ly:context-property voice prop #f)))
                         (if mlsm (set! melisma? #t))))
                     melisma-props))
                 voices))))
                 
       ;; clear `lyric-texts', `melisma?' before moving forward
       (stop-translation-timestep
        . ,(lambda (trans)
             (set! lyric-texts '())
             (set! melisma? #f))))))
             
%% Short-cut:
%% don't align next syllable to a melisma by `align-to-melisma-engraver'
do-not-align-me =
  \once \override LyricText.align-to-melisma = ##f

%%%%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLE
%%%%%%%%%%%%%%%%%%%%%%%%%

my-layout =
  \layout {
    \context {
      \Score
      \consists #align-to-melisma-engraver
      lyricMelismaAlignment = -0.5
    }
  }

one =
  \new Staff 
    \new Voice { 
      c''1( d'') 
      %\set Score.lyricMelismaAlignment = -2.5
      e''( f''2) e''
    }
  \addlyrics { 
    Left __ 
    Left __ 
  }
    
two = 
  \new Staff 
    \new Voice { 
      c''1 
      d'' 
      e'' f''2 e''
    }
  \addlyrics { 
    %% manual setting!
    \once \override LyricText.color = #cyan
    \once \override LyricText.self-alignment-X = #-2
    \do-not-align-me
    Left -- \markup \with-color #green "too!"
    Left too! 
  }
  
three =
  \new Staff
    \new Voice = "3" {
      c'1( d') 
      e'( f')
    }
  \addlyrics {
    very-long-one
    \markup \with-color #green
    "two" 
    three  four
  }
  
four =
  \new Staff
    \new Voice = "4" {
      c'1 d' 
      e' f'
    }
  \addlyrics {
    very-long-one
    \markup \with-color #red "two" 
    three four
  }
  
\score {
  <<
    \three
    \two
    \four
    \one
  >>
  \my-layout
}


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

Reply via email to