Initial clef change

Revision as of 22:47, 26 October 2025 by Jean Abou Samra (talk | contribs) (Import snippet from LSR)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Implements initial clef changes using a custom engraves. This engraves will upon encountering a difference between initial clef (as given by \with { \clef ... } and the clef at first timestep. If any difference is encountered a now clef (spaced like a cue clef) is created, while the original clef and any key signatures are modified to look like the initial clef values.

\version "2.24.0"

%%% This engraver records the initial clef properties (e.g. what is set by \with { \clef ... })
%%% If in the first timestep these changed, engrave the original clef, and change formatting and break
%%% alignment of the actual clef to mimic a clef change clef. Duplicates some procedure from clef engraver
%%% and could easily be integrated.
#(define (initial-clef-change-engraver context)
   (let ((initial-clef-properties #f) (cclef #f) (keysigs '()))
     ; macro for checking if any clef property has changed
     (define (clef-changed)
       (>
        (length initial-clef-properties)
        (length
         (filter
          (lambda (x) (equal? (cdr x) (ly:context-property context (car x))))
          initial-clef-properties))))
     (make-engraver
      ; Record initials propertis
      ((initialize engraver)
       (set!
        initial-clef-properties
        `((clefGlyph . ,(ly:context-property context 'clefGlyph))
          (clefPosition  . ,(ly:context-property context 'clefPosition))
          (middleCClefPosition . ,(ly:context-property context 'middleCClefPosition))
          (clefTransposition  . ,(ly:context-property context 'clefTransposition)))))
      ; Record the actual clef to adjust. Use details.muted to not acknowledge clef created by this engraver.
      (acknowledgers
       ((clef-interface engraver grob source-engraver)
        (if (not (assoc-get 'muted (ly:grob-property grob 'details) #f))
            (set! cclef grob)))
       ((key-signature-interface engraver grob source-engraver)
        (set! keysigs (cons grob keysigs))))
      ; Create a clef if necessary
      ((process-music engraver)
       (if (and initial-clef-properties (clef-changed))
           (let ((clef (ly:engraver-make-grob engraver 'Clef '())))
             (ly:grob-set-property! clef 'staff-position (assoc-get 'clefPosition initial-clef-properties))
             (ly:grob-set-property! clef 'glyph (assoc-get 'clefGlyph initial-clef-properties))
             (ly:grob-set-nested-property! clef '(details muted) #t)
             (if ((lambda (x) (and (number? x) (not (= 0 x))))
                  (assoc-get 'clefTransposition initial-clef-properties 0))
                 (let ((mod (ly:engraver-make-grob engraver 'ClefModifier '()))
                       (formatter (ly:context-property context 'clefTranspositionFormatter))
                       (style (ly:context-property context 'clefTranspositionStyle))
                       (dir (sign (assoc-get 'clefTransposition initial-clef-properties 0)))
                       (abs_trans (1+ (abs (assoc-get 'clefTransposition initial-clef-properties 0)))))
                   (if (procedure? formatter)
                       (ly:grob-set-property! mod 'text (formatter (number->string abs_trans) style)))
                   (ly:grob-set-object! mod 'side-support-elements (ly:grob-list->grob-array (list clef)))
                   (ly:grob-set-parent! mod X clef)
                   (ly:grob-set-parent! mod Y clef)
                   (ly:grob-set-property! mod 'direction dir))))))
      ; Adjust the actual clef and key signatures
      ((process-acknowledged engraver)
       (if (and cclef initial-clef-properties (clef-changed))
           (begin

            ; Key signatures need to be handled if they appear before the cue-clef
            ; This requires the break alignment information, so this only sets a
            ; hook which will be processed during `before-line-breaking` of the
            ; BreakAlignGroups
            (for-each
             (lambda (keysig)
               (let ((det (ly:grob-property keysig 'details))
                     (initial-clef-properties initial-clef-properties))
                 (ly:grob-set-property!
                  keysig
                  'details
                  (acons
                   'break-alignment-handler
                   (lambda (grob break-alignment)
                     (let ((start-of-line
                            (vector-ref
                             (ly:grob-property break-alignment 'break-align-orders)
                             2)))
                       (if (member 'cue-clef (or (member 'key-signature start-of-line) '()))
                           (ly:grob-set-property!
                            grob 'c0-position
                            (assoc-get 'middleCClefPosition initial-clef-properties)))))
                   det))))
             keysigs)

            (ly:grob-set-property! cclef 'non-default #t)
            (ly:grob-set-property! cclef 'break-align-symbol 'cue-clef)
            (if (not (eq? #t (ly:grob-property cclef 'full-size-change)))
                (ly:grob-set-property! cclef 'glyph-name
                                      (format #f "~a_change" (ly:grob-property cclef 'glyph)))))))
      ; Unset parameters
      ((stop-translation-timestep engraver)
       (set! initial-clef-properties #f)
       (set! clef #f)
       (set! keysigs '())))))

\layout {
  \context {
    \Staff
    \consists #initial-clef-change-engraver
  }
  \context {
    \Score
    \override BreakAlignGroup.before-line-breaking =
    #(lambda (grob)
       (let ((grobs (ly:grob-object grob 'elements))
             (break-alignment (ly:grob-parent grob X)))
         (if (not (null? grobs))
             (set! grobs (ly:grob-array->list grobs)))
         (for-each
          (lambda (grob)
            (let* ((det (ly:grob-property grob 'details))
                   (handler (assoc-get 'break-alignment-handler det)))
              (if (procedure? handler)
                  (handler grob break-alignment))))
          grobs)))
  }
}

\new Staff \with { \clef "bass^15" } {
  \key bes\major
  \clef "treble_8"
  c'1 c'1
  \clef bass
  c'1
}

\score {
  \layout {
    \context {
      \Score
      \override BreakAlignment.break-align-orders =
      ##((staff-ellipsis
          left-edge
          cue-end-clef
          ambitus
          breathing-sign
          optional-material-end-bracket
          signum-repetitionis
          clef
          cue-clef
          staff-bar
          key-cancellation
          key-signature
          time-signature
          optional-material-start-bracket
          custos)
         (staff-ellipsis
          left-edge
          optional-material-end-bracket
          cue-end-clef
          ambitus
          breathing-sign
          signum-repetitionis
          clef
          cue-clef
          staff-bar
          key-cancellation
          key-signature
          time-signature
          optional-material-start-bracket
          custos)
         (staff-ellipsis
          left-edge
          optional-material-end-bracket
          ambitus
          breathing-sign
          signum-repetitionis
          clef
          cue-clef
          key-cancellation
          key-signature
          time-signature
          staff-bar
          optional-material-start-bracket
          custos))
    }
  }
  \new Staff \with { \clef "bass^15" } {
    \key bes\major
    \clef "treble_8"
    c'1 c'1
    \clef bass
    c'1
  }
}