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