Re: Custom ambitus ?

Pierre-Luc Gauthier <[email protected]>
Newsgroups gmane.comp.gnu.lilypond.general
Message-ID <CAPkan7fMx8gayZaie=fOB1+Jy_4Ww0N18YPJ8zOuuYfxQ1yE4A@mail.gmail.com>
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
customAmbitus-2.ly (text/x-lilypond, 5.6 KB)
\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 }
customAmbitus-2.png (image/png, 36.2 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.