Jump to content

Basic grace echo purger: Difference between revisions

From LilyPond wiki
m Lemzwerg moved page Basic Grace Echo Purger to Basic grace echo purger without leaving a redirect: Only the first letter of a title should be uppercase
Revise; also make it work for metronome marks at the very beginning
Line 1: Line 1:
One subtype of the 'grace synchronisation issue' is the 'grace echo'. If a score wide collected event comes from at least one voice 'in time' while from one or more other voices it comes 'after the grace notes which do not exist in this/these voice(s)' the resulting grob will be created multiple times.<br />
One subtype of the infamous [https://gitlab.com/lilypond/lilypond/-/issues/34 grace synchronisation issue #34] is the ‘grace echo’: if a score-wide collected event comes from at least one voice ‘in time’, while from one or more other voices it comes ‘after the grace notes, which do not exist in this/these voice(s)’, the resulting grob gets created multiple times.
For metronome marks, bar lines and time signatures a very simple workaround is to just delete these echoed grobs. Here is an engraver which will do this job. Bar lines and time signatures will also be checked for equality and are only removed by this engraver if they are identical.<br />
Additionally, by setting a grob proptery #'keep-grace-echo = ##t you can inhibit this grob removal to obtain a special output.<br />
<br />
See also:


* '''[[LSR 978]]''' (Repeat commands grace echo purger) for voltas ('\repeat volta' commands)
For metronome marks, bar lines, and time signatures a very simple workaround is to delete these echoed grobs, which the engraver in this snippet does.  Bar lines and time signatures are checked also for equality and only removed by this engraver if they are identical.  Additionally, by setting the grob property <code>keep-grace-echo</code> to <code>#t</code>, you can inhibit this grob removal to obtain special output.
* '''[[LSR 976]]''' (Multiple simultaneous RehearsalMarks, tunable output per score, grace synchronized) for markup commands
 
See also snippet [[Repeat commands grace echo purger]], which handles a similar situation for  
<code>\repeat volta</code> commands.


<lilypond version="2.24">
<lilypond version="2.24">
%created by: ArnoldTheresius
#(define (Basic_grace_echo_purger ctx)
  (let ((last-main-moment -1)
        (current-main-moment -1)
        (barline-found #f)
        (purge-barline #f)
        (last-barline-type '())
        (current-barline-type '())
        (metronomemark-found #f)
        (purge-metronomemark #f)
        (timesignature-found #f)
        (timesignature-style-got '())
        (timesignature-style-found '())
        (timesignature-fraction-found '())
        (purge-timesignature #f)
        ;; Collected items that are eventually handled by
        ;; `ly:grob-suicide!`.
        (grobs-to-delete '()))


#(define (Basic_graceecho_purger ctx)
    `((acknowledgers
  (let ((last-main-moment -1)
        (bar-line-interface ; `BarLine` and `SpanBar` grobs
        (current-main-moment -1)
        . ,(lambda (trans grob source)
        (barline-found #f)
              (let ((keep-this-echo
        (purge-barline #f)
                    (ly:grob-property grob 'keep-grace-echo #f))
        (last-barline-type '())
                    (grob-name (grob::name grob)))
        (current-barline-type '())
                (when (equal? (symbol->string grob-name) "BarLine")
        (metronomemark-found #f)
                  (let ((barline-symbol (ly:grob-property grob
        (purge-metronomemark #f)
                                                          'glyph '())))
        (timesignature-found #f)
                    (when (not (null? barline-symbol))
        (timesignature-style-got '())
                      (if (and purge-barline
        (timesignature-style-found '())
                              (not keep-this-echo)
        (timesignature-fraction-found '())
                              (equal? last-barline-type
        (purge-timesignature #f)
                                      barline-symbol))
        (grobs-to-delete '()))
                          (begin
    `((acknowledgers
                            (ly:grob-set-property! grob 'glyph '())
      (bar-line-interface ; BarLines and SpanBars
                            (ly:context-set-property! ctx
        . ,(lambda (trans grob source)
                                                      'forbidBreak #t))
            (let
                          (begin
            ((keep-this-echo (ly:grob-property grob 'keep-grace-echo #f))
                            (set! barline-found #t)
              (grob-name (grob::name grob)))
                            (set! current-barline-type
            (if (equal? (symbol->string grob-name) "BarLine")
                                  barline-symbol)))))))))
               (let ((barline-symbol (ly:grob-property grob 'glyph '())))
 
              (if (not (null? barline-symbol))
        (metronome-mark-interface
                 (if (and purge-barline
        . ,(lambda (trans grob source)
              (let ((keep-this-echo
                    (ly:grob-property grob 'keep-grace-echo #f)))
                (if (and purge-metronomemark
                        (not keep-this-echo))
                    (set! grobs-to-delete (cons grob grobs-to-delete))
                    (set! metronomemark-found #t)))))
 
        (time-signature-interface
        . ,(lambda (trans grob source)
               (let ((keep-this-echo
                    (ly:grob-property grob 'keep-grace-echo #f)))
                 (if (and purge-timesignature
                         (not keep-this-echo)
                         (not keep-this-echo)
                         (equal? last-barline-type barline-symbol))
                         (equal? timesignature-style-found
                (begin
                                (ly:grob-property grob 'style))
                  (ly:grob-set-property! grob 'glyph '())
                        (equal? timesignature-fraction-found
                  (ly:context-set-property! ctx 'forbidBreak #t))
                                (ly:context-property
                (begin
                                  ctx 'timeSignatureFraction)))
                  (set! barline-found #t)
                    (set! grobs-to-delete (cons grob grobs-to-delete))
                  (set! current-barline-type barline-symbol))))))))
                    (begin
      ) ;; bar-line-interface
                      (set! timesignature-style-got
      (metronome-mark-interface
                            (ly:grob-property grob 'style))
        . ,(lambda (trans grob source)
                      (set! timesignature-found #t))))))
            (let ((keep-this-echo (ly:grob-property grob 'keep-grace-echo #f)))
        ) ; end of `acknowledgers` block
            (if (and purge-metronomemark (not keep-this-echo))
              (set! grobs-to-delete (cons grob grobs-to-delete)) ;; aka a delayed ly:grob-suicide!
              (set! metronomemark-found #t))))
      ) ;; metronome-mark-interface
      (time-signature-interface
        . ,(lambda (trans grob source)
            (let ((keep-this-echo (ly:grob-property grob 'keep-grace-echo #f)))
            (if (and purge-timesignature
                      (not keep-this-echo)
                      (equal? timesignature-style-found (ly:grob-property grob 'style))
                      (equal? timesignature-fraction-found (ly:context-property ctx 'timeSignatureFraction)))
              (set! grobs-to-delete (cons grob grobs-to-delete)) ;; aka a delayed ly:grob-suicide!
              (begin
              (set! timesignature-style-got (ly:grob-property grob 'style))
              (set! timesignature-found #t)))))
      ) ;; time-signature-interface
      ) ;; acknowledgers
      (start-translation-timestep
      . ,(lambda (trans)
          (let*
            ((now-mom (ly:context-current-moment ctx))
            (now-main-mom (ly:moment-main now-mom)))  ;; function missing in 2.14.2 !
            (set! current-main-moment now-main-mom)
            (if (equal? last-main-moment now-main-mom)
            (begin
              (if barline-found (set! purge-barline #t))
              (if metronomemark-found (set! purge-metronomemark #t))
              (if timesignature-found (begin
                (set! timesignature-fraction-found (ly:context-property ctx 'timeSignatureFraction))
                (set! timesignature-style-found timesignature-style-got)
                (set! purge-timesignature #t))))
            (begin
              (set! barline-found #f)
              (set! purge-barline #f)
              (set! metronomemark-found #f)
              (set! purge-metronomemark #f)
              (set! timesignature-found #f)
              (set! purge-timesignature #f)))))
      ) ;; start-translation-timestep
      (stop-translation-timestep
      . ,(lambda (trans)
          (for-each ly:grob-suicide! grobs-to-delete)
          (set! grobs-to-delete '())
          (set! last-barline-type current-barline-type)
          (set! last-main-moment current-main-moment))
      ) ;; stop-translation-timestep
    )))


% uncomment this to enable it by default, not only
      (start-translation-timestep
% if it's listed in the layout block of your scores.
        . ,(lambda (trans)
%{
            (let* ((now-mom (ly:context-current-moment ctx))
\layout {
                    (now-main-mom (ly:moment-main now-mom)))
  \context {
              (set! current-main-moment now-main-mom)
    \Score
              (if (or
    \consists #Basic_graceecho_purger
                    ;; Special case for the beginning of music.
  }
                    (and (equal? last-main-moment -1)
}
                        (equal? now-main-mom 0))
%}
                    ;; Normal case.
                    (equal? last-main-moment now-main-mom))
                  (begin
                    (when barline-found
                      (set! purge-barline #t))
                    (when metronomemark-found
                      (set! purge-metronomemark #t))
                    (when timesignature-found
                      (set! timesignature-fraction-found
                            (ly:context-property ctx
                                                  'timeSignatureFraction))
                      (set! timesignature-style-found
                            timesignature-style-got)
                      (set! purge-timesignature #t)))
                  (begin
                    (set! barline-found #f)
                    (set! purge-barline #f)
                    (set! metronomemark-found #f)
                    (set! purge-metronomemark #f)
                    (set! timesignature-found #f)
                    (set! purge-timesignature #f))))))
 
      (stop-translation-timestep
        . ,(lambda (trans)
            (for-each ly:grob-suicide! grobs-to-delete)
            (set! grobs-to-delete '())
            (set! last-barline-type current-barline-type)
            (set! last-main-moment current-main-moment)))
      )))


% This function is a copy from `scm/define-grob-properties.scm`.
#(define (define-grob-property symbol type? description)
#(define (define-grob-property symbol type? description)
  (if (not (equal? (object-property symbol 'backend-doc) #f))
  (if (not (equal? (object-property symbol 'backend-doc) #f))
      (ly:error (_ "symbol ~S redefined") symbol))
      (ly:error (G_ "symbol ~S redefined") symbol))


  (set-object-property! symbol 'backend-type? type?)
  (set-object-property! symbol 'backend-type? type?)
  (set-object-property! symbol 'backend-doc description)
  (set-object-property! symbol 'backend-doc description)
  symbol)
  symbol)
 
 
% A generic loop to set up grob properties.
#(for-each
#(for-each
   (lambda (x)
   (lambda (x)
     (apply define-grob-property x))
     (apply define-grob-property x))
    `((keep-grace-echo
  `((keep-grace-echo
      ,boolean?  
    ,boolean?
      "Do not purge this grob, it's wanted and not the result of a 'grace note  
    "Do not purge this grob, it is wanted and not the result of
echo' from different voices.")))
a 'grace note echo' from different voices.")))


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


% Example.


Music = {
Music = {
   % Test the keep-grace-echo property:
   % Test the `keep-grace-echo` property:
   % \once \override Score.MetronomeMark.keep-grace-echo = ##t
   % \once \override Score.MetronomeMark.keep-grace-echo = ##t
   \tempo "slow" 4 = 54
   \tempo "A"
   \partial 4
   \partial 4 \tag #'Grace \acciaccatura b8 c'4 |
  \tag #'Grace \acciaccatura b8 c'4
  \tempo "B"
   \tag #'Grace \acciaccatura b8 c'1
   \tag #'Grace \acciaccatura b8 c'1 |
   \repeat volta 2 {
   \repeat volta 2 {
     \tag #'Grace \acciaccatura b8 c'1
     \tag #'Grace \acciaccatura b8 c'1 |
   }
   }
   \tag #'Grace \acciaccatura b8 c'2
   \tag #'Grace \acciaccatura b8 c'2 \bar ";"
  \bar "|"
    \tempo "C"
  \tag #'Grace \acciaccatura b8 c'2
    \tag #'Grace \acciaccatura b8 c'2
  % Test the keep-grace-echo property:
    % Test the `keep-grace-echo` property:
  % \once \override Staff.TimeSignature.keep-grace-echo = ##t
    % \once \override Staff.TimeSignature.keep-grace-echo = ##t
  % \once \override Staff.BarLine.keep-grace-echo = ##t
    % \once \override Staff.BarLine.keep-grace-echo = ##t
  \bar "||"
    \bar "||"
   \time 3/4
   \time 3/4 \tag #'Grace \acciaccatura b8 c'2. \bar "|."
  \tag #'Grace \acciaccatura b8 c'2.
  \bar "|."
}
}


% Test: do not purge bar line or time signature if not equal
% Test: only purge identical bar lines and time signatures.
Appendix = {
Appendix = {
   \time 2/4 \grace { s128 \time 3/4 } e'2.
   \time 2/4 \grace { s128 \time 3/4 } e'2. |
   \time 4/4 \grace { s128 \numericTimeSignature \time 4/4 } g'1
   \time 4/4 \grace { s128 \numericTimeSignature \time 4/4 } g'1 \bar ";"
  \bar ";" \grace { s128 \bar "S-||" s128 \bar ".." } s128 \bar "|."
  \grace { s128 \bar "S-||" s128 \bar ".." } s128 \bar "|."
}
}


Group = \new StaffGroup <<
  \new Staff {
    \Music
    % Test_only: \Appendix
  }
  \new Staff {
    \removeWithTag #'Grace \Music
    % Text_only: \Appendix
  }
>>


\layout {
  indent = 0
  ragged-right = ##f
}
\markup { Without \typewriter Basic_grace_echo_purger }
\score {
\score {
   \new StaffGroup <<
   \Group
    \new Staff {
}
      \Music
 
      %Test_only: \Appendix
\markup { With \typewriter Basic_grace_echo_purger }
    }
\score {
    \new Staff {
  \Group
      \removeWithTag #'Grace \Music
      %Text_only: \Appendix
    }
  >>
   \layout {
   \layout {
     \context { \Score
     \context {
       \consists #Basic_graceecho_purger
      \Score
       \consists #Basic_grace_echo_purger
     }
     }
   }
   }
Line 176: Line 196:


[[Category:Contexts and engravers]]
[[Category:Contexts and engravers]]
[[Category:Contexts and engravers]]
[[Category:Scheme]]
[[Category:Workaround]]
[[Category:Workaround]]
[[Category:Snippet]]
[[Category:Snippet]]

Revision as of 14:52, 27 December 2025

One subtype of the infamous grace synchronisation issue #34 is the ‘grace echo’: if a score-wide collected event comes from at least one voice ‘in time’, while from one or more other voices it comes ‘after the grace notes, which do not exist in this/these voice(s)’, the resulting grob gets created multiple times.

For metronome marks, bar lines, and time signatures a very simple workaround is to delete these echoed grobs, which the engraver in this snippet does. Bar lines and time signatures are checked also for equality and only removed by this engraver if they are identical. Additionally, by setting the grob property keep-grace-echo to #t, you can inhibit this grob removal to obtain special output.

See also snippet Repeat commands grace echo purger, which handles a similar situation for \repeat volta commands.

\version "2.24"

#(define (Basic_grace_echo_purger ctx)
   (let ((last-main-moment -1)
         (current-main-moment -1)
         (barline-found #f)
         (purge-barline #f)
         (last-barline-type '())
         (current-barline-type '())
         (metronomemark-found #f)
         (purge-metronomemark #f)
         (timesignature-found #f)
         (timesignature-style-got '())
         (timesignature-style-found '())
         (timesignature-fraction-found '())
         (purge-timesignature #f)
         ;; Collected items that are eventually handled by
         ;; `ly:grob-suicide!`.
         (grobs-to-delete '()))

     `((acknowledgers
        (bar-line-interface ; `BarLine` and `SpanBar` grobs
         . ,(lambda (trans grob source)
              (let ((keep-this-echo
                     (ly:grob-property grob 'keep-grace-echo #f))
                    (grob-name (grob::name grob)))
                (when (equal? (symbol->string grob-name) "BarLine")
                  (let ((barline-symbol (ly:grob-property grob
                                                          'glyph '())))
                    (when (not (null? barline-symbol))
                      (if (and purge-barline
                               (not keep-this-echo)
                               (equal? last-barline-type
                                       barline-symbol))
                          (begin
                            (ly:grob-set-property! grob 'glyph '())
                            (ly:context-set-property! ctx
                                                      'forbidBreak #t))
                          (begin
                            (set! barline-found #t)
                            (set! current-barline-type
                                  barline-symbol)))))))))

        (metronome-mark-interface
         . ,(lambda (trans grob source)
              (let ((keep-this-echo
                     (ly:grob-property grob 'keep-grace-echo #f)))
                (if (and purge-metronomemark
                         (not keep-this-echo))
                    (set! grobs-to-delete (cons grob grobs-to-delete))
                    (set! metronomemark-found #t)))))

        (time-signature-interface
         . ,(lambda (trans grob source)
              (let ((keep-this-echo
                     (ly:grob-property grob 'keep-grace-echo #f)))
                (if (and purge-timesignature
                         (not keep-this-echo)
                         (equal? timesignature-style-found
                                 (ly:grob-property grob 'style))
                         (equal? timesignature-fraction-found
                                 (ly:context-property
                                  ctx 'timeSignatureFraction)))
                    (set! grobs-to-delete (cons grob grobs-to-delete))
                    (begin
                      (set! timesignature-style-got
                            (ly:grob-property grob 'style))
                      (set! timesignature-found #t))))))
        ) ; end of `acknowledgers` block

       (start-translation-timestep
        . ,(lambda (trans)
             (let* ((now-mom (ly:context-current-moment ctx))
                    (now-main-mom (ly:moment-main now-mom)))
               (set! current-main-moment now-main-mom)
               (if (or
                    ;; Special case for the beginning of music.
                    (and (equal? last-main-moment -1)
                         (equal? now-main-mom 0))
                    ;; Normal case.
                    (equal? last-main-moment now-main-mom))
                   (begin
                     (when barline-found
                       (set! purge-barline #t))
                     (when metronomemark-found
                       (set! purge-metronomemark #t))
                     (when timesignature-found
                       (set! timesignature-fraction-found
                             (ly:context-property ctx
                                                  'timeSignatureFraction))
                       (set! timesignature-style-found
                             timesignature-style-got)
                       (set! purge-timesignature #t)))
                   (begin
                     (set! barline-found #f)
                     (set! purge-barline #f)
                     (set! metronomemark-found #f)
                     (set! purge-metronomemark #f)
                     (set! timesignature-found #f)
                     (set! purge-timesignature #f))))))

       (stop-translation-timestep
        . ,(lambda (trans)
             (for-each ly:grob-suicide! grobs-to-delete)
             (set! grobs-to-delete '())
             (set! last-barline-type current-barline-type)
             (set! last-main-moment current-main-moment)))
       )))

% This function is a copy from `scm/define-grob-properties.scm`.
#(define (define-grob-property symbol type? description)
   (if (not (equal? (object-property symbol 'backend-doc) #f))
       (ly:error (G_ "symbol ~S redefined") symbol))

   (set-object-property! symbol 'backend-type? type?)
   (set-object-property! symbol 'backend-doc description)
   symbol)

% A generic loop to set up grob properties.
#(for-each
  (lambda (x)
    (apply define-grob-property x))
  `((keep-grace-echo
     ,boolean?
     "Do not purge this grob, it is wanted and not the result of
a 'grace note echo' from different voices.")))


% Example.

Music = {
  % Test the `keep-grace-echo` property:
  % \once \override Score.MetronomeMark.keep-grace-echo = ##t
  \tempo "A"
  \partial 4 \tag #'Grace \acciaccatura b8 c'4 |
  \tempo "B"
  \tag #'Grace \acciaccatura b8 c'1 |
  \repeat volta 2 {
    \tag #'Grace \acciaccatura b8 c'1 |
  }
  \tag #'Grace \acciaccatura b8 c'2 \bar ";"
    \tempo "C"
    \tag #'Grace \acciaccatura b8 c'2
    % Test the `keep-grace-echo` property:
    % \once \override Staff.TimeSignature.keep-grace-echo = ##t
    % \once \override Staff.BarLine.keep-grace-echo = ##t
    \bar "||"
  \time 3/4 \tag #'Grace \acciaccatura b8 c'2. \bar "|."
}

% Test: only purge identical bar lines and time signatures.
Appendix = {
  \time 2/4 \grace { s128 \time 3/4 } e'2. |
  \time 4/4 \grace { s128 \numericTimeSignature \time 4/4 } g'1 \bar ";"
  \grace { s128 \bar "S-||" s128 \bar ".." } s128 \bar "|."
}

Group = \new StaffGroup <<
  \new Staff {
    \Music
    % Test_only: \Appendix
  }
  \new Staff {
    \removeWithTag #'Grace \Music
    % Text_only: \Appendix
  }
>>

\layout {
  indent = 0
  ragged-right = ##f
}

\markup { Without \typewriter Basic_grace_echo_purger }
\score {
  \Group
}

\markup { With \typewriter Basic_grace_echo_purger }
\score {
  \Group
  \layout {
    \context {
      \Score
      \consists #Basic_grace_echo_purger
    }
  }
}