Jump to content

Defining an engraver in Scheme: ambitus engraver: Difference between revisions

From LilyPond wiki
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 @var{grob-item} to the array of conditional elements of @var{grob}.
   "Add GROB-ITEM to the array of conditional elements of GROB.
Rewrite of @code{Separation_item::add_conditional_item} from @file{lily/separation-item.cc}."
 
   (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 @var{accidental-grob}.
   "Get the pitch from the grob cause of ACCIDENTAL-GROB.
Rewrite of @code{accidental_pitch} from @file{lily/accidental-placement.cc}."
 
   (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 @var{accidental-grob}, an @code{Accidental} grob, to the
   "Add ACCIDENTAL-GROB to the list of accidentals grobs of GROB.
list of the accidental grobs of @var{grob}, an @code{AccidentalPlacement}
ACCIDENTAL-GROB is an `Accidental` grob; GROB is an `AccidentalPlacement`
grob.
grob.
Rewrite of @code{Accidental_placement::add_accidental} from @file{lily/accidental-placement.cc}."
 
   (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 structure
%%% Ambitus data structures.
%%%
%%%


%%% The <ambitus> class holds the various grobs that are created
%%% The <ambitus> class holds the various grobs that are created to
%%% to print an ambitus:
%%% print an ambitus:
%%% - ambitus-group: the grob that groups all the components of an ambitus
%%%
%%% (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 (`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 accidental
%%%   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
%%% below).
%%%   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. It
%%%
%%% 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;
%%% is used to determine if accidentals shall be printed next to ambitus
%%% - `start-key-sig`: the key signature at the beginning of the
%%% notes.
%%%  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)
   "If @var{direction} is @code{UP}, then return the upper ambitus note
   "Return lower or upper note of AMBITUS depending on DIRECTION."
of @var{ambitus}, otherwise return the lower ambitus note."
   (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 ambitus
%%% 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 printed next
%%% - `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. the lower
%%% 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 logics
%%% Ambitus engraving logic.
%%%
%%%
%%% Rewrite of the code from @file{lily/ambitus-engraver.cc}.
%%% 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:


The Ambitus grob contain all other grobs:
  Ambitus
  Ambitus
   |- AmbitusLine
   |- AmbitusLine
   |- AmbitusNoteHead   for upper note
   |- AmbitusNoteHead   for upper note
   |- AmbitusAccidental for upper note
   |- AmbitusAccidental for upper note
   |- AmbitusNoteHead   for lower note
   |- 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-grob of the note head
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
   ;; Make the ambitus object.
   (let ((ambitus (make <ambitus>)))
   (let ((ambitus (make <ambitus>)))
     ;; build the Ambitus grob, which will contain all other grobs
     ;; Build the `Ambitus` grob, which will contain all other grobs.
     (set! (ambitus-group ambitus) (ly:engraver-make-grob translator 'Ambitus '()))
     (set! (ambitus-group ambitus)
     ;; build the AmbitusLine grob (line between lower and upper note)
          (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
     ;; build the upper and lower AmbitusNoteHead and AmbitusAccidental
    ;; note).
     (for-each (lambda (direction)
     (set! (ambitus-line ambitus)
                (let ((head (ly:engraver-make-grob translator 'AmbitusNoteHead '()))
          (ly:engraver-make-grob translator 'AmbitusLine '()))
                      (accidental (ly:engraver-make-grob translator 'AmbitusAccidental '()))
     ;; Build the upper and lower `AmbitusNoteHead` and
                      (group (ambitus-group ambitus)))
    ;; `AmbitusAccidental`.
                  ;; The parent of the AmbitusAccidental grob is the
     (for-each
                  ;; AmbitusNoteHead grob
      (lambda (direction)
                  (set! (ly:grob-parent accidental Y) head)
        (let ((head (ly:engraver-make-grob translator
                  ;; The AmbitusAccidental grob is set as the accidental-grob
                                          'AmbitusNoteHead '()))
                  ;; object of the AmbitusNoteHead.  This is later used by the
              (accidental (ly:engraver-make-grob translator
                  ;; function that prints notes.
                                                'AmbitusAccidental '()))
                  (set! (ly:grob-object head 'accidental-grob) accidental)
              (group (ambitus-group ambitus)))
                  ;; both the note head and the accidental grobs are added
          ;; The parent of the `AmbitusAccidental` grob is the
                  ;; to the main ambitus grob.
          ;; `AmbitusNoteHead` grob.
                  (ly:axis-group-interface::add-element group head)
          (set! (ly:grob-parent accidental Y) head)
                  (ly:axis-group-interface::add-element group accidental)
          ;; The `AmbitusAccidental` grob is set as the
                  ;; the note head and the accidental grobs are added to the
          ;; `accidental-grob` object of `AmbitusNoteHead`.  This is
                  ;; ambitus object
          ;; later used by the function that prints notes.
                  (set! (ambitus-note-head (ambitus-note ambitus direction))
          (set! (ly:grob-object head 'accidental-grob) accidental)
                        head)
          ;; Both the note head and the accidental grobs are added to
                  (set! (ambitus-note-accidental (ambitus-note ambitus direction))
          ;; the main ambitus grob.
                        accidental)))
          (ly:axis-group-interface::add-element group head)
              (list DOWN UP))
          (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
     ;; 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 @var{ambitus}, by getting the starting
                (ambitus <ambitus>) translator)
position of middle C and key signature from @var{translator}'s context."
   "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))
                                    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 the upper and lower ambitus pithes of @var{ambitus}, using
   "Update upper and lower ambitus pitches of AMBITUS using NOTE-GROB."
@var{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`.
   ;; and check 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
         ;; 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
           ;; 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
           ;; then this pitch is the new ambitus lower pitch,
           ;; this pitch is the new ambitus' lower pitch.  The same is
           ;; and conversely for upper pitch.
           ;; done for the upper pitch (but in the opposite
           (for-each (lambda (direction pitch-compare)
          ;; direction).
                      (if (or (not (ambitus-note-pitch (ambitus-note ambitus direction)))
           (for-each
                              (pitch-compare pitch
            (lambda (direction pitch-compare)
                                              (ambitus-note-pitch (ambitus-note ambitus direction))))
              (if (or (not (ambitus-note-pitch
                          (begin
                            (ambitus-note ambitus direction)))
                            (set! (ambitus-note-pitch (ambitus-note ambitus direction))
                      (pitch-compare
                                  pitch)
                      pitch (ambitus-note-pitch
                            (set! (ambitus-note-cause (ambitus-note ambitus direction))
                              (ambitus-note ambitus direction))))
                                  note-event))))
                  (begin
                    (list DOWN UP)
                    (set! (ambitus-note-pitch
                    (list ly:pitch<? (lambda (p1 p2)
                          (ambitus-note ambitus direction))
                                        (ly:pitch<? p2 p1))))))))
                          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 the ambitus:
   "Typeset AMBITUS.
- place the lower and upper ambitus notes according to their pitch and
 
   the position of the middle C;
- Place the lower and upper ambitus notes according to their pitch and
- typeset or delete the note accidentals, according to the key signature.
   the position of the middle C.
  An accidental, if it is to be printed, is added to an AccidentalPlacement
- 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
- both note heads are added to the ambitus line grob, so that a line should
   `AccidentalPlacement` grob (a grob dedicated to the placement of
   be printed between them."
  accidentals near a chord).
   ;; check if there are lower and upper pitches
- 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 accidentals
       ;; Make an `AccidentalPlacement` grob, for placement of note
       (let ((accidental-placement (ly:engraver-make-grob
      ;; accidentals.
                                    translator
       (let ((accidental-placement
                                    'AccidentalPlacement
              (ly:engraver-make-grob
                                    (ambitus-note-accidental (ambitus-note ambitus DOWN)))))
              translator
         ;; For lower and upper ambitus notes:
              'AccidentalPlacement (ambitus-note-accidental
         (for-each (lambda (direction)
                                    (ambitus-note ambitus DOWN)))))
                    (let ((pitch (ambitus-note-pitch (ambitus-note ambitus direction))))
         ;; For lower and upper ambitus notes.
                      ;; set the cause and the staff position of the ambitus note
         (for-each
                      ;; according to the associated pitch
          (lambda (direction)
                      (set! (ly:grob-property (ambitus-note-head (ambitus-note ambitus direction))
            (let ((pitch (ambitus-note-pitch
                                              'cause)
                          (ambitus-note ambitus direction))))
                            (ambitus-note-cause (ambitus-note ambitus direction)))
              ;; Set the cause and the staff position of the ambitus
                      (set! (ly:grob-property (ambitus-note-head (ambitus-note ambitus direction))
              ;; note according to the associated pitch.
                                              'staff-position)
              (set! (ly:grob-property
                            (+ (ambitus-start-c0 ambitus)
                    (ambitus-note-head (ambitus-note ambitus direction))
                                (ly:pitch-steps pitch)))
                    'cause)
                      ;; determine if an accidental shall be printed for this note,
                    (ambitus-note-cause (ambitus-note ambitus direction)))
                      ;; according to the key signature
              (set! (ly:grob-property
                      (let* ((handle (or (assoc (cons (ly:pitch-octave pitch)
                    (ambitus-note-head (ambitus-note ambitus direction))
                                                      (ly:pitch-notename pitch))
                    'staff-position)
                                                (ambitus-start-key-sig ambitus))
                    (+ (ambitus-start-c0 ambitus)
                                          (assoc (ly:pitch-notename pitch)
                      (ly:pitch-steps pitch)))
                                                (ambitus-start-key-sig ambitus))))
              ;; Determine whether an accidental shall be printed for
                              (sig-alter (if handle (cdr handle) 0)))
              ;; this note, according to the key signature.
                        (cond ((= (ly:pitch-alteration pitch) sig-alter)
              (let* ((handle
                                ;; the note alteration is in the key signature
                      (or (assoc (cons (ly:pitch-octave pitch)
                                ;; => it does not have to be printed
                                      (ly:pitch-notename pitch))
                                (ly:grob-suicide!
                                (ambitus-start-key-sig ambitus))
                                (ambitus-note-accidental (ambitus-note ambitus direction)))
                          (assoc (ly:pitch-notename pitch)
                                (set! (ly:grob-object (ambitus-note-head (ambitus-note ambitus direction))
                                (ambitus-start-key-sig ambitus))))
                                                      'accidental-grob)
                    (sig-alter (if handle (cdr handle) 0)))
                                      '()))
                (cond
                              (else
                ((= (ly:pitch-alteration pitch) sig-alter)
                                ;; otherwise, the accidental shall be printed
                  ;; The note alteration is in the key signature
                                (set! (ly:grob-property (ambitus-note-accidental
                  ;; => it does not have to be printed.
                                                        (ambitus-note ambitus direction))
                  (ly:grob-suicide! (ambitus-note-accidental
                                                        'alteration)
                                    (ambitus-note ambitus direction)))
                                      (ly:pitch-alteration pitch)))))
                  (set! (ly:grob-object (ambitus-note-head
                      ;; add the AccidentalPlacement grob to the
                                        (ambitus-note ambitus direction))
                      ;; conditional items of the AmbitusNoteHead
                                        'accidental-grob)
                      (ly:separation-item::add-conditional-item
                        '()))
                        (ambitus-note-head (ambitus-note ambitus direction))
                (else
                        accidental-placement)
                  ;; Otherwise the accidental shall be printed.
                      ;; add the AmbitusAccidental to the list of the
                  (set! (ly:grob-property
                      ;; AccidentalPlacement grob accidentals
                        (ambitus-note-accidental
                      (ly:accidental-placement::add-accidental
                          (ambitus-note ambitus direction)) 'alteration)
                        accidental-placement
                        (ly:pitch-alteration pitch)))))
                        (ambitus-note-accidental (ambitus-note ambitus direction)))
              ;; Add the `AccidentalPlacement` grob to the conditional
                      ;; add the AmbitusNoteHead grob to the AmbitusLine grob
              ;; items of the `AmbitusNoteHead`.
                      (ly:pointer-group-interface::add-grob
              (ly:separation-item::add-conditional-item
                        (ambitus-line ambitus)
              (ambitus-note-head (ambitus-note ambitus direction))
                        'note-heads
              accidental-placement)
                        (ambitus-note-head (ambitus-note ambitus direction)))))
              ;; Add the `AmbitusAccidental` to the list of the
                  (list DOWN UP))
              ;; `AccidentalPlacement` grob accidentals.
         ;; add the AccidentalPlacement grob to the main Ambitus grob
              (ly:accidental-placement::add-accidental
         (ly:axis-group-interface::add-element (ambitus-group ambitus) accidental-placement))
              accidental-placement
       ;; no notes ==> suicide the grobs
              (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
                    (ly:grob-suicide! (ambitus-note-accidental (ambitus-note ambitus direction)))
          (lambda (direction)
                    (ly:grob-suicide! (ambitus-note-head (ambitus-note ambitus direction))))
            (ly:grob-suicide! (ambitus-note-accidental
                  (list DOWN UP))
                              (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
       ;; When music is processed, make the ambitus object if not
      ;; already built.
       (make-engraver
       (make-engraver
    ((process-music translator)
        ((process-music translator)
    (if (not ambitus)
        (if (not ambitus)
        (set! ambitus (make-ambitus translator))))
            (set! ambitus (make-ambitus translator))))
    ;; set the ambitus clef and key signature state
 
    ((stop-translation-timestep translator)
        ;; Set the ambitus clef and key signature state.
    (if ambitus
        ((stop-translation-timestep translator)
        (initialize-ambitus-state ambitus translator)))
        (if ambitus
    ;; when a note-head grob is built, update the ambitus notes
            (initialize-ambitus-state ambitus translator)))
    (acknowledgers
 
          ((note-head-interface engraver grob source-engraver)
        ;; When a note head grob is built, update the ambitus notes.
      (if ambitus
        (acknowledgers
          (update-ambitus-notes ambitus grob))))
        ((note-head-interface engraver grob source-engraver)
    ;; finally, typeset the ambitus according to its upper and lower notes
          (if ambitus
    ;; (if any).
              (update-ambitus-notes ambitus grob))))
    ((finalize translator)
 
    (if ambitus
        ;; Finally, typeset the ambitus according to its upper and
        (typeset-ambitus ambitus translator)))))))
        ;; 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 } }
}