Defining an engraver in Scheme: ambitus engraver: Difference between revisions
Appearance
m New category |
Formatting, improving documentation |
||
| Line 7: | Line 7: | ||
%%% Grob utilities | %%% Grob utilities | ||
%%% | %%% | ||
%%% These are literal rewrites of some C++ methods used by the ambitus engraver. | %%% These are literal rewrites of some C++ methods used by the ambitus | ||
%%% engraver. | |||
#(define (ly:separation-item::add-conditional-item grob grob-item) | #(define (ly:separation-item::add-conditional-item grob grob-item) | ||
"Add | "Add GROB-ITEM to the array of conditional elements of GROB. | ||
(ly:pointer-group-interface::add-grob grob 'conditional-elements grob-item)) | This is a rewrite of function `Separation_item::add_conditional_item` from | ||
file `lily/separation-item.cc`." | |||
(ly:pointer-group-interface::add-grob | |||
grob 'conditional-elements grob-item)) | |||
#(define (ly:accidental-placement::accidental-pitch accidental-grob) | #(define (ly:accidental-placement::accidental-pitch accidental-grob) | ||
"Get the pitch from the grob cause of | "Get the pitch from the grob cause of ACCIDENTAL-GROB. | ||
(ly:event-property (ly:grob-property (ly:grob-parent accidental-grob Y) 'cause) | This is a rewrite of function `accidental_pitch` from file | ||
`lily/accidental-placement.cc`." | |||
(ly:event-property (ly:grob-property | |||
(ly:grob-parent accidental-grob Y) 'cause) | |||
'pitch)) | 'pitch)) | ||
#(define (ly:accidental-placement::add-accidental grob accidental-grob) | #(define (ly:accidental-placement::add-accidental grob accidental-grob) | ||
"Add | "Add ACCIDENTAL-GROB to the list of accidentals grobs of GROB. | ||
list of | ACCIDENTAL-GROB is an `Accidental` grob; GROB is an `AccidentalPlacement` | ||
grob. | grob. | ||
(let ((pitch (ly:accidental-placement::accidental-pitch accidental-grob))) | This is a rewrite of function `Accidental_placement::add_accidental` from | ||
file `lily/accidental-placement.cc`." | |||
(let ((pitch (ly:accidental-placement::accidental-pitch | |||
accidental-grob))) | |||
(set! (ly:grob-parent accidental-grob X) grob) | (set! (ly:grob-parent accidental-grob X) grob) | ||
(let* ((accidentals (ly:grob-object grob 'accidental-grobs)) | (let* ((accidentals (ly:grob-object grob 'accidental-grobs)) | ||
| Line 36: | Line 46: | ||
%%% | %%% | ||
%%% Ambitus data | %%% Ambitus data structures. | ||
%%% | %%% | ||
%%% The <ambitus> class holds the various grobs that are created | %%% The <ambitus> class holds the various grobs that are created to | ||
%%% | %%% print an ambitus: | ||
%%% - ambitus-group: the grob that groups all the components of an | %%% | ||
%%% (Ambitus grob); | %%% - `ambitus-group`: the grob that groups all the components of an | ||
%%% - ambitus-line: the vertical line between the upper and lower | %%% ambitus (`Ambitus` grob); | ||
%%% notes (AmbitusLine grob); | %%% - `ambitus-line`: the vertical line between the upper and lower | ||
%%% - ambitus-up-note and ambitus-down-note: the note head and | %%% ambitus notes (`AmbitusLine` grob); | ||
%%% for the lower and upper note of the ambitus (see <ambitus-note> class | %%% - `ambitus-up-note` and `ambitus-down-note`: the note head and | ||
%%% | %%% accidental for the lower and upper note of the ambitus (see | ||
%%% `<ambitus-note>` class below). | |||
%%% | |||
%%% The other slots define the key and clef context of the engraver: | %%% The other slots define the key and clef context of the engraver: | ||
%%% - start-c0: position of middle c at the beginning of the piece. | %%% | ||
%%% is used to place the ambitus notes according to their pitch; | %%% - `start-c0`: position of middle c at the beginning of the piece. | ||
%%% - start-key-sig: the key signature at the beginning of the piece. It | %%% It is used to place the ambitus notes according to their pitch; | ||
%%% - `start-key-sig`: the key signature at the beginning of the | |||
%%% piece. It is used to determine whether accidentals shall be | |||
%%% printed next to ambitus notes. | |||
#(define-class <ambitus> () | #(define-class <ambitus> () | ||
| Line 67: | Line 80: | ||
#:init-value '())) | #:init-value '())) | ||
%%% Accessor for the lower and upper note data of an ambitus | %%% Accessor for the lower and upper note data of an ambitus. | ||
#(define-method (ambitus-note (ambitus <ambitus>) direction) | #(define-method (ambitus-note (ambitus <ambitus>) direction) | ||
" | "Return lower or upper note of AMBITUS depending on DIRECTION." | ||
of | |||
(if (= direction UP) | (if (= direction UP) | ||
(ambitus-up-note ambitus) | (ambitus-up-note ambitus) | ||
(ambitus-down-note ambitus))) | (ambitus-down-note ambitus))) | ||
%%% The <ambitus-note> class holds the grobs that are specific to | %%% The `<ambitus-note>` class holds the grobs that are specific to | ||
%%% (lower and upper) notes: | %%% ambitus (lower and upper) notes: | ||
%%% - head: an AmbitusNoteHead grob; | %%% | ||
%%% - accidental: an AmbitusAccidental grob, to be possibly | %%% - `head`: an `AmbitusNoteHead` grob; | ||
%%% to the ambitus note head. | %%% - `accidental`: an `AmbitusAccidental` grob, to be possibly | ||
%%% Moreover | %%% printed next to the ambitus note head. | ||
%%% - pitch is the absolute pitch of the note | %%% | ||
%%% - cause is the note event that causes this ambitus note, i.e. | %%% Moreover, | ||
%%% or upper note of the considered music sequence. | %%% | ||
%%% - `pitch` is the absolute pitch of the note; | |||
%%% - `cause` is the note event that causes this ambitus note, i.e., | |||
%%% the lower or upper note of the considered music sequence. | |||
#(define-class <ambitus-note> () | #(define-class <ambitus-note> () | ||
| Line 96: | Line 111: | ||
%%% | %%% | ||
%%% Ambitus engraving | %%% Ambitus engraving logic. | ||
%%% | %%% | ||
%%% | %%% This is rewrite of the code from file `lily/ambitus-engraver.cc`. | ||
#(define (make-ambitus translator) | #(define (make-ambitus translator) | ||
"Build an ambitus object: initialize all the grobs and their relations. | "Build an ambitus object: initialize all the grobs and their | ||
relations. | |||
The `Ambitus` grob contains all other grobs: | |||
Ambitus | Ambitus | ||
|- AmbitusLine | |- AmbitusLine | ||
|- AmbitusNoteHead | |- AmbitusNoteHead for upper note | ||
|- AmbitusAccidental for upper note | |- AmbitusAccidental for upper note | ||
|- AmbitusNoteHead | |- AmbitusNoteHead for lower note | ||
|- AmbitusAccidental for lower note | |- AmbitusAccidental for lower note | ||
The parent of an accidental is the corresponding note head, | The parent of an accidental is the corresponding note head, and the | ||
and the accidental is set as the | accidental is set as the `accidental-grob` property of the note head | ||
so that is printed by the function that prints notes." | so that is printed by the function that prints notes." | ||
;; | ;; Make the ambitus object. | ||
(let ((ambitus (make <ambitus>))) | (let ((ambitus (make <ambitus>))) | ||
;; | ;; Build the `Ambitus` grob, which will contain all other grobs. | ||
(set! (ambitus-group ambitus) (ly:engraver-make-grob translator 'Ambitus '())) | (set! (ambitus-group ambitus) | ||
;; | (ly:engraver-make-grob translator 'Ambitus '())) | ||
(set! (ambitus-line ambitus) (ly:engraver-make-grob translator 'AmbitusLine '())) | ;; Build the `AmbitusLine` grob (the line between lower and upper | ||
;; | ;; note). | ||
(for-each (lambda (direction) | (set! (ambitus-line ambitus) | ||
(ly:engraver-make-grob translator 'AmbitusLine '())) | |||
;; Build the upper and lower `AmbitusNoteHead` and | |||
;; `AmbitusAccidental`. | |||
(for-each | |||
(lambda (direction) | |||
(let ((head (ly:engraver-make-grob translator | |||
'AmbitusNoteHead '())) | |||
(accidental (ly:engraver-make-grob translator | |||
'AmbitusAccidental '())) | |||
(group (ambitus-group ambitus))) | |||
;; The parent of the `AmbitusAccidental` grob is the | |||
;; `AmbitusNoteHead` grob. | |||
(set! (ly:grob-parent accidental Y) head) | |||
;; The `AmbitusAccidental` grob is set as the | |||
;; `accidental-grob` object of `AmbitusNoteHead`. This is | |||
;; later used by the function that prints notes. | |||
(set! (ly:grob-object head 'accidental-grob) accidental) | |||
;; Both the note head and the accidental grobs are added to | |||
;; the main ambitus grob. | |||
(ly:axis-group-interface::add-element group head) | |||
(ly:axis-group-interface::add-element group accidental) | |||
;; The parent of the ambitus line is the lower ambitus note head | ;; The note head and the accidental grobs are added to the | ||
;; ambitus object. | |||
(set! (ambitus-note-head (ambitus-note ambitus direction)) | |||
head) | |||
(set! (ambitus-note-accidental (ambitus-note ambitus direction)) | |||
accidental))) | |||
(list DOWN UP)) | |||
;; The parent of the ambitus line is the lower ambitus note head. | |||
(set! (ly:grob-parent (ambitus-line ambitus) X) | (set! (ly:grob-parent (ambitus-line ambitus) X) | ||
(ambitus-note-head (ambitus-note ambitus DOWN))) | (ambitus-note-head (ambitus-note ambitus DOWN))) | ||
;; | ;; The ambitus line is added to the ambitus main grob. | ||
(ly:axis-group-interface::add-element (ambitus-group ambitus) (ambitus-line ambitus)) | (ly:axis-group-interface::add-element (ambitus-group ambitus) | ||
(ambitus-line ambitus)) | |||
ambitus)) | ambitus)) | ||
#(define-method (initialize-ambitus-state (ambitus <ambitus>) translator) | #(define-method (initialize-ambitus-state | ||
"Initialize the state of | (ambitus <ambitus>) translator) | ||
position of middle C and key signature from | "Initialize the state of AMBITUS by getting the starting position of | ||
middle C and key signature from TRANSLATOR's context." | |||
(if (not (ambitus-start-c0 ambitus)) | (if (not (ambitus-start-c0 ambitus)) | ||
(begin | (begin | ||
(set! (ambitus-start-c0 ambitus) | (set! (ambitus-start-c0 ambitus) | ||
(ly:context-property (ly:translator-context translator) | (ly:context-property (ly:translator-context translator) | ||
'middleCPosition | 'middleCPosition 0)) | ||
(set! (ambitus-start-key-sig ambitus) | (set! (ambitus-start-key-sig ambitus) | ||
(ly:context-property (ly:translator-context translator) | (ly:context-property (ly:translator-context translator) | ||
| Line 164: | Line 190: | ||
#(define-method (update-ambitus-notes (ambitus <ambitus>) note-grob) | #(define-method (update-ambitus-notes (ambitus <ambitus>) note-grob) | ||
"Update | "Update upper and lower ambitus pitches of AMBITUS using NOTE-GROB." | ||
;; Get the event that caused the `note-grob` creation and check | |||
;; Get the event that caused the note-grob creation | ;; that it is a `note-event`. | ||
;; | |||
(let ((note-event (ly:grob-property note-grob 'cause))) | (let ((note-event (ly:grob-property note-grob 'cause))) | ||
(if (ly:in-event-class? note-event 'note-event) | (if (ly:in-event-class? note-event 'note-event) | ||
;; | ;; Get the pitch from the note event. | ||
(let ((pitch (ly:event-property note-event 'pitch))) | (let ((pitch (ly:event-property note-event 'pitch))) | ||
;; | ;; If this pitch is lower than the current ambitus' lower | ||
;; note pitch (or it has not been initialized yet), | ;; note pitch (or it has not been initialized yet), then | ||
;; | ;; this pitch is the new ambitus' lower pitch. The same is | ||
;; | ;; done for the upper pitch (but in the opposite | ||
(for-each (lambda (direction pitch-compare) | ;; direction). | ||
(for-each | |||
(lambda (direction pitch-compare) | |||
(if (or (not (ambitus-note-pitch | |||
(ambitus-note ambitus direction))) | |||
(pitch-compare | |||
pitch (ambitus-note-pitch | |||
(ambitus-note ambitus direction)))) | |||
(begin | |||
(set! (ambitus-note-pitch | |||
(ambitus-note ambitus direction)) | |||
pitch) | |||
(set! (ambitus-note-cause | |||
(ambitus-note ambitus direction)) | |||
note-event)))) | |||
(list DOWN UP) | |||
(list ly:pitch<? | |||
(lambda (p1 p2) (ly:pitch<? p2 p1)))))))) | |||
#(define-method (typeset-ambitus (ambitus <ambitus>) translator) | #(define-method (typeset-ambitus (ambitus <ambitus>) translator) | ||
"Typeset | "Typeset AMBITUS. | ||
- | |||
the position of the middle C | - Place the lower and upper ambitus notes according to their pitch and | ||
- | the position of the middle C. | ||
- Typeset or delete the note accidentals, according to the key | |||
grob (a grob dedicated to the placement of accidentals near a chord) | signature. An accidental, if it is to be printed, is added to an | ||
- | `AccidentalPlacement` grob (a grob dedicated to the placement of | ||
accidentals near a chord). | |||
;; | - Both note heads are added to the ambitus line grob so that a line | ||
gets printed between them." | |||
;; Check whether there are lower and upper pitches. | |||
(if (and (ambitus-note-pitch (ambitus-note ambitus UP)) | (if (and (ambitus-note-pitch (ambitus-note ambitus UP)) | ||
(ambitus-note-pitch (ambitus-note ambitus DOWN))) | (ambitus-note-pitch (ambitus-note ambitus DOWN))) | ||
;; | ;; Make an `AccidentalPlacement` grob, for placement of note | ||
(let ((accidental-placement (ly:engraver-make-grob | ;; accidentals. | ||
(let ((accidental-placement | |||
(ly:engraver-make-grob | |||
translator | |||
;; For lower and upper ambitus notes | 'AccidentalPlacement (ambitus-note-accidental | ||
(for-each (lambda (direction) | (ambitus-note ambitus DOWN))))) | ||
;; For lower and upper ambitus notes. | |||
(for-each | |||
(lambda (direction) | |||
(let ((pitch (ambitus-note-pitch | |||
(ambitus-note ambitus direction)))) | |||
;; Set the cause and the staff position of the ambitus | |||
;; note according to the associated pitch. | |||
(set! (ly:grob-property | |||
(ambitus-note-head (ambitus-note ambitus direction)) | |||
'cause) | |||
(ambitus-note-cause (ambitus-note ambitus direction))) | |||
(set! (ly:grob-property | |||
(ambitus-note-head (ambitus-note ambitus direction)) | |||
'staff-position) | |||
(+ (ambitus-start-c0 ambitus) | |||
(ly:pitch-steps pitch))) | |||
;; Determine whether an accidental shall be printed for | |||
;; this note, according to the key signature. | |||
(let* ((handle | |||
(or (assoc (cons (ly:pitch-octave pitch) | |||
(ly:pitch-notename pitch)) | |||
(ambitus-start-key-sig ambitus)) | |||
(assoc (ly:pitch-notename pitch) | |||
(ambitus-start-key-sig ambitus)))) | |||
(sig-alter (if handle (cdr handle) 0))) | |||
(cond | |||
((= (ly:pitch-alteration pitch) sig-alter) | |||
;; The note alteration is in the key signature | |||
;; => it does not have to be printed. | |||
(ly:grob-suicide! (ambitus-note-accidental | |||
(ambitus-note ambitus direction))) | |||
(set! (ly:grob-object (ambitus-note-head | |||
(ambitus-note ambitus direction)) | |||
'accidental-grob) | |||
'())) | |||
(else | |||
;; Otherwise the accidental shall be printed. | |||
(set! (ly:grob-property | |||
(ambitus-note-accidental | |||
(ambitus-note ambitus direction)) 'alteration) | |||
(ly:pitch-alteration pitch))))) | |||
;; Add the `AccidentalPlacement` grob to the conditional | |||
;; items of the `AmbitusNoteHead`. | |||
(ly:separation-item::add-conditional-item | |||
(ambitus-note-head (ambitus-note ambitus direction)) | |||
accidental-placement) | |||
;; Add the `AmbitusAccidental` to the list of the | |||
;; `AccidentalPlacement` grob accidentals. | |||
;; | (ly:accidental-placement::add-accidental | ||
(ly:axis-group-interface::add-element (ambitus-group ambitus) accidental-placement)) | accidental-placement | ||
;; | (ambitus-note-accidental (ambitus-note ambitus direction))) | ||
;; Add the `AmbitusNoteHead` grob to the `AmbitusLine` grob. | |||
(ly:pointer-group-interface::add-grob | |||
(ambitus-line ambitus) | |||
'note-heads | |||
(ambitus-note-head (ambitus-note ambitus direction))))) | |||
(list DOWN UP)) | |||
;; Add the `AccidentalPlacement` grob to the main `Ambitus` grob. | |||
(ly:axis-group-interface::add-element | |||
(ambitus-group ambitus) accidental-placement)) | |||
;; No lower and upper pitches => nothing to print. | |||
(begin | (begin | ||
(for-each (lambda (direction) | (for-each | ||
(lambda (direction) | |||
(ly:grob-suicide! (ambitus-note-accidental | |||
(ambitus-note ambitus direction))) | |||
(ly:grob-suicide! (ambitus-note-head | |||
(ambitus-note ambitus direction)))) | |||
(list DOWN UP)) | |||
(ly:grob-suicide! ambitus-line)))) | (ly:grob-suicide! ambitus-line)))) | ||
%%% | %%% | ||
%%% Ambitus engraver definition | %%% Ambitus engraver definition. | ||
%%% | %%% | ||
#(define ambitus-engraver | #(define ambitus-engraver | ||
(lambda (context) | (lambda (context) | ||
(let ((ambitus #f)) | (let ((ambitus #f)) | ||
;; | ;; When music is processed, make the ambitus object if not | ||
;; already built. | |||
(make-engraver | (make-engraver | ||
((process-music translator) | |||
(if (not ambitus) | |||
(set! ambitus (make-ambitus translator)))) | |||
;; Set the ambitus clef and key signature state. | |||
((stop-translation-timestep translator) | |||
(if ambitus | |||
(initialize-ambitus-state ambitus translator))) | |||
;; When a note head grob is built, update the ambitus notes. | |||
(acknowledgers | |||
((note-head-interface engraver grob source-engraver) | |||
(if ambitus | |||
(update-ambitus-notes ambitus grob)))) | |||
;; Finally, typeset the ambitus according to its upper and | |||
;; lower notes (if any). | |||
((finalize translator) | |||
(if ambitus | |||
(typeset-ambitus ambitus translator))))))) | |||
%%% | %%% | ||
Revision as of 20:10, 16 December 2025
This example demonstrates how the ambitus engraver may be defined on the user side, with a Scheme engraver. This is basically a rewrite in Scheme of the code from lily/ambitus-engraver.cc.
\version "2.24"
#(use-modules (oop goops))
%%%
%%% Grob utilities
%%%
%%% These are literal rewrites of some C++ methods used by the ambitus
%%% engraver.
#(define (ly:separation-item::add-conditional-item grob grob-item)
"Add GROB-ITEM to the array of conditional elements of GROB.
This is a rewrite of function `Separation_item::add_conditional_item` from
file `lily/separation-item.cc`."
(ly:pointer-group-interface::add-grob
grob 'conditional-elements grob-item))
#(define (ly:accidental-placement::accidental-pitch accidental-grob)
"Get the pitch from the grob cause of ACCIDENTAL-GROB.
This is a rewrite of function `accidental_pitch` from file
`lily/accidental-placement.cc`."
(ly:event-property (ly:grob-property
(ly:grob-parent accidental-grob Y) 'cause)
'pitch))
#(define (ly:accidental-placement::add-accidental grob accidental-grob)
"Add ACCIDENTAL-GROB to the list of accidentals grobs of GROB.
ACCIDENTAL-GROB is an `Accidental` grob; GROB is an `AccidentalPlacement`
grob.
This is a rewrite of function `Accidental_placement::add_accidental` from
file `lily/accidental-placement.cc`."
(let ((pitch (ly:accidental-placement::accidental-pitch
accidental-grob)))
(set! (ly:grob-parent accidental-grob X) grob)
(let* ((accidentals (ly:grob-object grob 'accidental-grobs))
(handle (assq (ly:pitch-notename pitch) accidentals))
(entry (if handle (cdr handle) '())))
(set! (ly:grob-object grob 'accidental-grobs)
(assq-set! accidentals
(ly:pitch-notename pitch)
(cons accidental-grob entry))))))
%%%
%%% Ambitus data structures.
%%%
%%% The <ambitus> class holds the various grobs that are created to
%%% print an ambitus:
%%%
%%% - `ambitus-group`: the grob that groups all the components of an
%%% ambitus (`Ambitus` grob);
%%% - `ambitus-line`: the vertical line between the upper and lower
%%% ambitus notes (`AmbitusLine` grob);
%%% - `ambitus-up-note` and `ambitus-down-note`: the note head and
%%% accidental for the lower and upper note of the ambitus (see
%%% `<ambitus-note>` class below).
%%%
%%% The other slots define the key and clef context of the engraver:
%%%
%%% - `start-c0`: position of middle c at the beginning of the piece.
%%% It is used to place the ambitus notes according to their pitch;
%%% - `start-key-sig`: the key signature at the beginning of the
%%% piece. It is used to determine whether accidentals shall be
%%% printed next to ambitus notes.
#(define-class <ambitus> ()
(ambitus-group #:accessor ambitus-group)
(ambitus-line #:accessor ambitus-line)
(ambitus-up-note #:getter ambitus-up-note
#:init-form (make <ambitus-note>))
(ambitus-down-note #:getter ambitus-down-note
#:init-form (make <ambitus-note>))
(start-c0 #:accessor ambitus-start-c0
#:init-value #f)
(start-key-sig #:accessor ambitus-start-key-sig
#:init-value '()))
%%% Accessor for the lower and upper note data of an ambitus.
#(define-method (ambitus-note (ambitus <ambitus>) direction)
"Return lower or upper note of AMBITUS depending on DIRECTION."
(if (= direction UP)
(ambitus-up-note ambitus)
(ambitus-down-note ambitus)))
%%% The `<ambitus-note>` class holds the grobs that are specific to
%%% ambitus (lower and upper) notes:
%%%
%%% - `head`: an `AmbitusNoteHead` grob;
%%% - `accidental`: an `AmbitusAccidental` grob, to be possibly
%%% printed next to the ambitus note head.
%%%
%%% Moreover,
%%%
%%% - `pitch` is the absolute pitch of the note;
%%% - `cause` is the note event that causes this ambitus note, i.e.,
%%% the lower or upper note of the considered music sequence.
#(define-class <ambitus-note> ()
(head #:accessor ambitus-note-head
#:init-value #f)
(accidental #:accessor ambitus-note-accidental
#:init-value #f)
(cause #:accessor ambitus-note-cause
#:init-value #f)
(pitch #:accessor ambitus-note-pitch
#:init-value #f))
%%%
%%% Ambitus engraving logic.
%%%
%%% This is rewrite of the code from file `lily/ambitus-engraver.cc`.
#(define (make-ambitus translator)
"Build an ambitus object: initialize all the grobs and their
relations.
The `Ambitus` grob contains all other grobs:
Ambitus
|- AmbitusLine
|- AmbitusNoteHead for upper note
|- AmbitusAccidental for upper note
|- AmbitusNoteHead for lower note
|- AmbitusAccidental for lower note
The parent of an accidental is the corresponding note head, and the
accidental is set as the `accidental-grob` property of the note head
so that is printed by the function that prints notes."
;; Make the ambitus object.
(let ((ambitus (make <ambitus>)))
;; Build the `Ambitus` grob, which will contain all other grobs.
(set! (ambitus-group ambitus)
(ly:engraver-make-grob translator 'Ambitus '()))
;; Build the `AmbitusLine` grob (the line between lower and upper
;; note).
(set! (ambitus-line ambitus)
(ly:engraver-make-grob translator 'AmbitusLine '()))
;; Build the upper and lower `AmbitusNoteHead` and
;; `AmbitusAccidental`.
(for-each
(lambda (direction)
(let ((head (ly:engraver-make-grob translator
'AmbitusNoteHead '()))
(accidental (ly:engraver-make-grob translator
'AmbitusAccidental '()))
(group (ambitus-group ambitus)))
;; The parent of the `AmbitusAccidental` grob is the
;; `AmbitusNoteHead` grob.
(set! (ly:grob-parent accidental Y) head)
;; The `AmbitusAccidental` grob is set as the
;; `accidental-grob` object of `AmbitusNoteHead`. This is
;; later used by the function that prints notes.
(set! (ly:grob-object head 'accidental-grob) accidental)
;; Both the note head and the accidental grobs are added to
;; the main ambitus grob.
(ly:axis-group-interface::add-element group head)
(ly:axis-group-interface::add-element group accidental)
;; The note head and the accidental grobs are added to the
;; ambitus object.
(set! (ambitus-note-head (ambitus-note ambitus direction))
head)
(set! (ambitus-note-accidental (ambitus-note ambitus direction))
accidental)))
(list DOWN UP))
;; The parent of the ambitus line is the lower ambitus note head.
(set! (ly:grob-parent (ambitus-line ambitus) X)
(ambitus-note-head (ambitus-note ambitus DOWN)))
;; The ambitus line is added to the ambitus main grob.
(ly:axis-group-interface::add-element (ambitus-group ambitus)
(ambitus-line ambitus))
ambitus))
#(define-method (initialize-ambitus-state
(ambitus <ambitus>) translator)
"Initialize the state of AMBITUS by getting the starting position of
middle C and key signature from TRANSLATOR's context."
(if (not (ambitus-start-c0 ambitus))
(begin
(set! (ambitus-start-c0 ambitus)
(ly:context-property (ly:translator-context translator)
'middleCPosition 0))
(set! (ambitus-start-key-sig ambitus)
(ly:context-property (ly:translator-context translator)
'keyAlterations)))))
#(define-method (update-ambitus-notes (ambitus <ambitus>) note-grob)
"Update upper and lower ambitus pitches of AMBITUS using NOTE-GROB."
;; Get the event that caused the `note-grob` creation and check
;; that it is a `note-event`.
(let ((note-event (ly:grob-property note-grob 'cause)))
(if (ly:in-event-class? note-event 'note-event)
;; Get the pitch from the note event.
(let ((pitch (ly:event-property note-event 'pitch)))
;; If this pitch is lower than the current ambitus' lower
;; note pitch (or it has not been initialized yet), then
;; this pitch is the new ambitus' lower pitch. The same is
;; done for the upper pitch (but in the opposite
;; direction).
(for-each
(lambda (direction pitch-compare)
(if (or (not (ambitus-note-pitch
(ambitus-note ambitus direction)))
(pitch-compare
pitch (ambitus-note-pitch
(ambitus-note ambitus direction))))
(begin
(set! (ambitus-note-pitch
(ambitus-note ambitus direction))
pitch)
(set! (ambitus-note-cause
(ambitus-note ambitus direction))
note-event))))
(list DOWN UP)
(list ly:pitch<?
(lambda (p1 p2) (ly:pitch<? p2 p1))))))))
#(define-method (typeset-ambitus (ambitus <ambitus>) translator)
"Typeset AMBITUS.
- Place the lower and upper ambitus notes according to their pitch and
the position of the middle C.
- Typeset or delete the note accidentals, according to the key
signature. An accidental, if it is to be printed, is added to an
`AccidentalPlacement` grob (a grob dedicated to the placement of
accidentals near a chord).
- Both note heads are added to the ambitus line grob so that a line
gets printed between them."
;; Check whether there are lower and upper pitches.
(if (and (ambitus-note-pitch (ambitus-note ambitus UP))
(ambitus-note-pitch (ambitus-note ambitus DOWN)))
;; Make an `AccidentalPlacement` grob, for placement of note
;; accidentals.
(let ((accidental-placement
(ly:engraver-make-grob
translator
'AccidentalPlacement (ambitus-note-accidental
(ambitus-note ambitus DOWN)))))
;; For lower and upper ambitus notes.
(for-each
(lambda (direction)
(let ((pitch (ambitus-note-pitch
(ambitus-note ambitus direction))))
;; Set the cause and the staff position of the ambitus
;; note according to the associated pitch.
(set! (ly:grob-property
(ambitus-note-head (ambitus-note ambitus direction))
'cause)
(ambitus-note-cause (ambitus-note ambitus direction)))
(set! (ly:grob-property
(ambitus-note-head (ambitus-note ambitus direction))
'staff-position)
(+ (ambitus-start-c0 ambitus)
(ly:pitch-steps pitch)))
;; Determine whether an accidental shall be printed for
;; this note, according to the key signature.
(let* ((handle
(or (assoc (cons (ly:pitch-octave pitch)
(ly:pitch-notename pitch))
(ambitus-start-key-sig ambitus))
(assoc (ly:pitch-notename pitch)
(ambitus-start-key-sig ambitus))))
(sig-alter (if handle (cdr handle) 0)))
(cond
((= (ly:pitch-alteration pitch) sig-alter)
;; The note alteration is in the key signature
;; => it does not have to be printed.
(ly:grob-suicide! (ambitus-note-accidental
(ambitus-note ambitus direction)))
(set! (ly:grob-object (ambitus-note-head
(ambitus-note ambitus direction))
'accidental-grob)
'()))
(else
;; Otherwise the accidental shall be printed.
(set! (ly:grob-property
(ambitus-note-accidental
(ambitus-note ambitus direction)) 'alteration)
(ly:pitch-alteration pitch)))))
;; Add the `AccidentalPlacement` grob to the conditional
;; items of the `AmbitusNoteHead`.
(ly:separation-item::add-conditional-item
(ambitus-note-head (ambitus-note ambitus direction))
accidental-placement)
;; Add the `AmbitusAccidental` to the list of the
;; `AccidentalPlacement` grob accidentals.
(ly:accidental-placement::add-accidental
accidental-placement
(ambitus-note-accidental (ambitus-note ambitus direction)))
;; Add the `AmbitusNoteHead` grob to the `AmbitusLine` grob.
(ly:pointer-group-interface::add-grob
(ambitus-line ambitus)
'note-heads
(ambitus-note-head (ambitus-note ambitus direction)))))
(list DOWN UP))
;; Add the `AccidentalPlacement` grob to the main `Ambitus` grob.
(ly:axis-group-interface::add-element
(ambitus-group ambitus) accidental-placement))
;; No lower and upper pitches => nothing to print.
(begin
(for-each
(lambda (direction)
(ly:grob-suicide! (ambitus-note-accidental
(ambitus-note ambitus direction)))
(ly:grob-suicide! (ambitus-note-head
(ambitus-note ambitus direction))))
(list DOWN UP))
(ly:grob-suicide! ambitus-line))))
%%%
%%% Ambitus engraver definition.
%%%
#(define ambitus-engraver
(lambda (context)
(let ((ambitus #f))
;; When music is processed, make the ambitus object if not
;; already built.
(make-engraver
((process-music translator)
(if (not ambitus)
(set! ambitus (make-ambitus translator))))
;; Set the ambitus clef and key signature state.
((stop-translation-timestep translator)
(if ambitus
(initialize-ambitus-state ambitus translator)))
;; When a note head grob is built, update the ambitus notes.
(acknowledgers
((note-head-interface engraver grob source-engraver)
(if ambitus
(update-ambitus-notes ambitus grob))))
;; Finally, typeset the ambitus according to its upper and
;; lower notes (if any).
((finalize translator)
(if ambitus
(typeset-ambitus ambitus translator)))))))
%%%
%%% Example
%%%
\score {
\new StaffGroup <<
\new Staff { c'4 des' e' fis' gis' }
\new Staff { \clef "bass" c4 des ~ des ees b, }
>>
\layout { \context { \Staff \consists #ambitus-engraver } }
}