Thank you Tina ! It works perfectly. I will also use it for some bell
setups. Quite nice.

Here are my modifications to your original code might anyone be interested.

\version "2.27.3"

#(define (instrument-setup-engraver context)
   (let ((setup-grob #f) (setup-head #f) (pitches (make-hash-table))
(is-typeset? #f) (start-c0 #f))
     (make-engraver
      ((process-music engraver)
       (if (not setup-grob)
           (begin
            ;; Reusing the Ambitus/AmbitusNoteHead grob types purely for their
            ;; existing side-position/break-alignment defaults (placement
            ;; before the clef, spacing, collision-avoidance). Their stencils
            ;; are fully overridden below -- this is not an ambitus display.
            (set! setup-grob (ly:engraver-make-grob engraver 'Ambitus '()))
            (set! setup-head (ly:engraver-make-grob engraver
'AmbitusNoteHead '()))
            (ly:grob-set-object! setup-grob 'elements
(ly:grob-list->grob-array (list setup-head)))
            (ly:grob-set-property! setup-head 'stencil ly:text-interface::print)
            (ly:grob-set-parent! setup-head X setup-grob)
            (set! is-typeset? #f))))
      ((stop-translation-timestep engraver)
       (if (and setup-grob (not is-typeset?))
           (let*
            ((c_pos (ly:context-property context 'middleCPosition))
             (cue_pos (ly:context-property context 'middleCCuePosition)))
            (set!
             start-c0
             (cond ((ly:context-property context 'ottavaStartNow #f)
                    (ly:context-property context 'middleCClefPosition 0))
                   ((and (integer? c_pos) (not (integer? cue_pos)))
                    c_pos)
                   (else
                    (+ (ly:context-property context 'middleCClefPosition 0)
                       (ly:context-property context 'middleCOffset 0)))))
            (set! is-typeset? #t))))
      (acknowledgers
       ((note-head-interface engraver grob source)
        (let* ((cause (ly:grob-property grob 'cause))
               (pitch (and (ly:stream-event? cause) (ly:event-property
cause 'pitch))))
          (if (and pitch (not (hash-ref pitches pitch #f)))
              (hash-set! pitches pitch cause)))))
      ((finalize engraver)
       (let* ((note-name-alterations (make-hash-table))
              (sorted-pitches
               (sort (hash-map->list (lambda (k v) k) pitches)
                     (lambda (a b) (ly:pitch<? a b))))
              (abs-step
               (lambda (p) (+ (* (ly:pitch-octave p) 7) (ly:pitch-notename p))))
              ;; split the sorted pitches into runs of consecutive
              ;; staff-position seconds ("clusters"); isolated notes end
              ;; up as their own cluster of size 1
              (clusters
               (let loop ((lst sorted-pitches) (cur '()) (acc '()))
                 (cond
                  ((null? lst)
                   (reverse (if (null? cur) acc (cons (reverse cur) acc))))
                  ((null? cur)
                   (loop (cdr lst) (list (car lst)) acc))
                  (else
                   (if (= (- (abs-step (car lst)) (abs-step (car cur))) 1)
                       (loop (cdr lst) (cons (car lst) cur) acc)
                       (loop (cdr lst) (list (car lst)) (cons (reverse
cur) acc)))))))
              (offset-table (make-hash-table)))
         ;; within each cluster, nudge every other note rightward so
         ;; touching noteheads visually separate from their neighbor
         (for-each
          (lambda (cluster)
            (let loop ((notes cluster) (idx 0))
              (if (pair? notes)
                  (begin
                   (hash-set! offset-table (car notes) (if (even? idx) 0 0.6))
                   (loop (cdr notes) (+ idx 1))))))
          clusters)
         ;; collect every distinct alteration used per note-name, to know
         ;; which pitches need a forced accidental to disambiguate
         (for-each
          (lambda (pitch)
            (let* ((notename (ly:pitch-notename pitch))
                   (alteration (ly:pitch-alteration pitch))
                   (existing (hash-ref note-name-alterations notename '())))
              (if (not (member alteration existing))
                  (hash-set! note-name-alterations notename (cons
alteration existing)))))
          sorted-pitches)
         (ly:grob-set-property!
          setup-head
          'text
          (make-score-markup
           #{
             \score {
               \layout {
                 indent = 0
               }
               \new Staff \with {
                 \remove #instrument-setup-engraver
                 middleCPosition = #start-c0
                 \remove Clef_engraver
                 \remove Time_signature_engraver
                 \omit Stem
                 \omit StaffSymbol
               } {
                 #(make-music
                   'EventChord
                   'elements
                   (map
                    (lambda (pitch)
                      (let* ((notename (ly:pitch-notename pitch))
                             (alterations (hash-ref
note-name-alterations notename '()))
                             (conflict? (> (length alterations) 1))
                             (x-offset (hash-ref offset-table pitch 0)))
                        (make-music
                         'NoteEvent
                         'duration
                         (ly:make-duration 2)
                         'pitch
                         pitch
                         'force-accidental
                         conflict?
                         'tweaks
                         (list (cons 'extra-offset (cons x-offset 0))))))
                    sorted-pitches))
               }
             }
           #})))))))

\layout {
  \context {
    \Staff
    \consists #instrument-setup-engraver
  }
}

{ \clef bass g, c d e gis a fis }


Le sam. 8 août 2026, à 10 h 04, Tina Petzel <[email protected]> a écrit :
>
> Hi Pierre,
>
> Am Freitag, 7. August 2026, 22:51:14 Mitteleuropäische Sommerzeit schrieb
> Pierre-Luc Gauthier:
> > Hi everyone,
> >
> > Is there a way to inject specific notes in the "Ambitus_engraver" ? I
> > guess then it would be trivial to gather all the different notes in a
> > music expression.
>
> It would be quite simple to write an engraver that listens for all unique
> pitches. The problem is then what to do with it. The ambitus system inplace
> does is not really designed to handle multi-note setting, such as collisions 
> and
> stuff. Instead I suppose it would be much easier to inject a score-markup with
> a chord of all occurring pitches.
>
> The appended file does exactly that. What it does not do yet is to match all
> possibly relevant settings between the markup-score and the original staff 
> (staff
> line positions and such).
>
> Cheers,
> Tina



-- 
Pierre-Luc Gauthier
\version "2.27.3"

#(define (instrument-setup-engraver context)
   (let ((setup-grob #f) (setup-head #f) (pitches (make-hash-table)) (is-typeset? #f) (start-c0 #f))
     (make-engraver
      ((process-music engraver)
       (if (not setup-grob)
           (begin
            ;; Reusing the Ambitus/AmbitusNoteHead grob types purely for their
            ;; existing side-position/break-alignment defaults (placement
            ;; before the clef, spacing, collision-avoidance). Their stencils
            ;; are fully overridden below -- this is not an ambitus display.
            (set! setup-grob (ly:engraver-make-grob engraver 'Ambitus '()))
            (set! setup-head (ly:engraver-make-grob engraver 'AmbitusNoteHead '()))
            (ly:grob-set-object! setup-grob 'elements (ly:grob-list->grob-array (list setup-head)))
            (ly:grob-set-property! setup-head 'stencil ly:text-interface::print)
            (ly:grob-set-parent! setup-head X setup-grob)
            (set! is-typeset? #f))))
      ((stop-translation-timestep engraver)
       (if (and setup-grob (not is-typeset?))
           (let*
            ((c_pos (ly:context-property context 'middleCPosition))
             (cue_pos (ly:context-property context 'middleCCuePosition)))
            (set!
             start-c0
             (cond ((ly:context-property context 'ottavaStartNow #f)
                    (ly:context-property context 'middleCClefPosition 0))
                   ((and (integer? c_pos) (not (integer? cue_pos)))
                    c_pos)
                   (else
                    (+ (ly:context-property context 'middleCClefPosition 0)
                       (ly:context-property context 'middleCOffset 0)))))
            (set! is-typeset? #t))))
      (acknowledgers
       ((note-head-interface engraver grob source)
        (let* ((cause (ly:grob-property grob 'cause))
               (pitch (and (ly:stream-event? cause) (ly:event-property cause 'pitch))))
          (if (and pitch (not (hash-ref pitches pitch #f)))
              (hash-set! pitches pitch cause)))))
      ((finalize engraver)
       (let* ((note-name-alterations (make-hash-table))
              (sorted-pitches
               (sort (hash-map->list (lambda (k v) k) pitches)
                     (lambda (a b) (ly:pitch<? a b))))
              (abs-step
               (lambda (p) (+ (* (ly:pitch-octave p) 7) (ly:pitch-notename p))))
              ;; split the sorted pitches into runs of consecutive
              ;; staff-position seconds ("clusters"); isolated notes end
              ;; up as their own cluster of size 1
              (clusters
               (let loop ((lst sorted-pitches) (cur '()) (acc '()))
                 (cond
                  ((null? lst)
                   (reverse (if (null? cur) acc (cons (reverse cur) acc))))
                  ((null? cur)
                   (loop (cdr lst) (list (car lst)) acc))
                  (else
                   (if (= (- (abs-step (car lst)) (abs-step (car cur))) 1)
                       (loop (cdr lst) (cons (car lst) cur) acc)
                       (loop (cdr lst) (list (car lst)) (cons (reverse cur) acc)))))))
              (offset-table (make-hash-table)))
         ;; within each cluster, nudge every other note rightward so
         ;; touching noteheads visually separate from their neighbor
         (for-each
          (lambda (cluster)
            (let loop ((notes cluster) (idx 0))
              (if (pair? notes)
                  (begin
                   (hash-set! offset-table (car notes) (if (even? idx) 0 0.6))
                   (loop (cdr notes) (+ idx 1))))))
          clusters)
         ;; collect every distinct alteration used per note-name, to know
         ;; which pitches need a forced accidental to disambiguate
         (for-each
          (lambda (pitch)
            (let* ((notename (ly:pitch-notename pitch))
                   (alteration (ly:pitch-alteration pitch))
                   (existing (hash-ref note-name-alterations notename '())))
              (if (not (member alteration existing))
                  (hash-set! note-name-alterations notename (cons alteration existing)))))
          sorted-pitches)
         (ly:grob-set-property!
          setup-head
          'text
          (make-score-markup
           #{
             \score {
               \layout {
                 indent = 0
               }
               \new Staff \with {
                 \remove #instrument-setup-engraver
                 middleCPosition = #start-c0
                 \remove Clef_engraver
                 \remove Time_signature_engraver
                 \omit Stem
                 \omit StaffSymbol
               } {
                 #(make-music
                   'EventChord
                   'elements
                   (map
                    (lambda (pitch)
                      (let* ((notename (ly:pitch-notename pitch))
                             (alterations (hash-ref note-name-alterations notename '()))
                             (conflict? (> (length alterations) 1))
                             (x-offset (hash-ref offset-table pitch 0)))
                        (make-music
                         'NoteEvent
                         'duration
                         (ly:make-duration 2)
                         'pitch
                         pitch
                         'force-accidental
                         conflict?
                         'tweaks
                         (list (cons 'extra-offset (cons x-offset 0))))))
                    sorted-pitches))
               }
             }
           #})))))))

\layout {
  \context {
    \Staff
    \consists #instrument-setup-engraver
  }
}

{ \clef bass g, c d e gis a fis }

Reply via email to