Adding ornamentations to note heads (part 2)

In the previous post, we’ve set up a basic way to add a pincé on the left edge of a note head. Now, we’ll see how to improve this functionality, to make it more general, so that one should be able to define several ornamentations, and place them on the left or the right edge of note heads.

New ornamentations definition

Specifying the appearance

We would like to define several kinds of ornamentations, besides the pincé we’ve implemented (its appearance was hardcoded in the text property of HeadOrnamentation grob definition). We will thus use a text property in the HeadOrnamentationEvent, and set the HeadOrnamentation text property, in the engraver.

The definition of the ornamentation commands would look like:

pince =
#(make-music 'HeadOrnamentationEvent
             'text  #{ \markup\concat{
                         \fontsize #-3 \rotate #190 \musicglyph #"scripts.rcomma"
                         \hspace #0.3 } #})
cadence =
#(make-music 'HeadOrnamentationEvent
             'text #{ \markup\concat {
                        \fontsize #-3 \musicglyph #"scripts.prall"
                        \hspace #0.5 } #})

Now that the events contain the look of the ornamentation in their text property, the engraver can use them to set the graphical objects shape:

%% The head-ornamentation engraver, with its note-head acknowledger
%% (which adds HeadOrnamentation grobs to note heads)
#(define head-ornamentation-engraver
   (make-engraver
    (acknowledgers
     ((note-head-interface engraver note-grob source)
      ;; When the note head has ornamentation events among its articulations,
      ;; then create a HeadOrnamentation grob and attach it to the Head grob
      (let ((note-event (ly:grob-property note-grob 'cause)))
        (for-each
         (lambda (articulation)
           (if (memq 'head-ornamentation-event
                     (ly:event-property articulation 'class))
               ;; this articulation is an ornementation => make the grob
               (let ((ornament-grob (ly:engraver-make-grob engraver
                                                           'HeadOrnamentation
                                                           note-grob)))
                 ;; use the ornament event text to set the grob text property
                 (set! (ly:grob-property ornament-grob 'text)
                       (ly:event-property articulation 'text))                 (ly:pointer-group-interface::add-grob ornament-grob
                                                       'elements
                                                       note-grob)
                 ;; the ornamentation is vertically aligned with the note head
                 (set! (ly:grob-parent ornament-grob Y) note-grob)
                 ;; the font size is relative to the note head's one
                 (set! (ly:grob-property ornament-grob 'font-size)
                       (+ (ly:grob-property ornament-grob 'font-size 0.0)
                          (ly:grob-property note-grob 'font-size 0.0))))))
         (ly:event-property note-event 'articulations)))))))

The following example (complete file: simple-ornaments.ly):

{ g'2\pince c''\cadence }

will now give the following result:

simple-ornaments.preview

Left and right directions

The next step is to add support for ornamentations on the right side of note heads, or on both sides at once. For instance, c'\pinceLR would produce parentheses both on the left and right sides of the note. So, instead of a single text property in the HeadOrnamentationEvent, two properties will be used: left-text and right-text. The new ornamentation commands look like this:

pinceLR =
#(make-music 'HeadOrnamentationEvent
             'left-text #{ \markup\concat {    \fontsize #-3 \rotate #190 \musicglyph #"scripts.rcomma"
    \hspace #0.5 } #}
             'right-text #{  \markup \rotate #10 \fontsize #-3 \musicglyph #"scripts.rcomma" #})

pinceR =
#(make-music 'HeadOrnamentationEvent
             'right-text #{  \markup \rotate #10 \fontsize #-3 \musicglyph #"scripts.rcomma" #})

cadenceL =
#(make-music 'HeadOrnamentationEvent
             'left-text #{ \markup\concat {    \fontsize #-3 \musicglyph #"scripts.prall"
    \hspace #0.5 } #})

The engraver, when dealing with HeadOrnamentationEvents, will create HeadOrnamentation grobs on the left or right direction, depending on the value of the left-text and right-text properties:

#(define head-ornamentation-engraver
   (make-engraver
    (acknowledgers
     ((note-head-interface engraver note-grob source)
      ;; helper function to create HeadOrnamentation grobs
      (define (make-ornament-grob text direction)        (let ((ornament-grob (ly:engraver-make-grob engraver
                                                    'HeadOrnamentation
                                                    note-grob)))
          ;; use the given text as the grob text property
          (set! (ly:grob-property ornament-grob 'text) text)
          ;; set the grob direction (either LEFT or RIGHT)
          (set! (ly:grob-property ornament-grob 'direction) direction)          (ly:pointer-group-interface::add-grob ornament-grob
                                                'elements
                                                note-grob)
          ;; the ornamentation is vertically aligned with the note head
          (set! (ly:grob-parent ornament-grob Y) note-grob)
          ;; compute its font size
          (set! (ly:grob-property ornament-grob 'font-size)
                (+ (ly:grob-property ornament-grob 'font-size 0.0)
                   (ly:grob-property note-grob 'font-size 0.0)))
          ornament-grob))
      ;; When the note-head event attached to the note-head grob has
      ;; ornamentation events among its articulations, then create a
      ;; HeadOrnamentation grob
      (for-each
       (lambda (articulation)
         (if (memq 'head-ornamentation-event
                   (ly:event-property articulation 'class))
             ;; this articulation is an ornamentation => make the grob
             ;; (either on LEFT or RIGHT direction)
             (begin
               (if (markup? (ly:event-property articulation 'left-text))
                   (make-ornament-grob
                     (ly:event-property articulation 'left-text)
                     LEFT))
               (if (markup? (ly:event-property articulation 'right-text))
                   (make-ornament-grob
                     (ly:event-property articulation 'right-text)
                     RIGHT)))))         (ly:event-property (ly:grob-property note-grob 'cause)
                            'articulations))))))

Finally, the HeadOrnamentation print method will be aware of the grob direction, to place it accurately:

#(define (head-ornamentation::print me)
   "Prints a HeadOrnamentation grob, on the left or right side of the
note head, depending on the grob direction."
   (let* ((notes (ly:grob-object me 'elements))
          (y-ref (ly:grob-common-refpoint-of-array me notes Y))
          (x-ref (ly:grob-common-refpoint-of-array me notes X))
          (x-ext (ly:relative-group-extent notes x-ref X))
          (y-ext (ly:relative-group-extent notes y-ref Y))
          (y-coord (interval-center y-ext))
          (text (ly:text-interface::print me))
          (width (/ (interval-length (ly:stencil-extent text X)) 2.0))
          (x-coord (if (= (ly:grob-property me 'direction) LEFT)
                       (- (car x-ext) width)
                       (+ (cdr x-ext) width))))     (ly:stencil-translate
      text
      (cons
       (- x-coord (ly:grob-relative-coordinate me x-ref X))
       (- y-coord (ly:grob-relative-coordinate me y-ref Y))))))

We’re already not far from the desired output (complete file left-right-ornaments.ly):

{
  \clef "treble"
  \key g \major
  \once\override Staff.TimeSignature #'style = #'single-digit
  \time 2/2 \partial 4
  d''4 | \stemUp
  g'1\pinceR ^\markup\musicglyph #"scripts.segno" |
  << b'4\rest \\ e'\pinceLR>> e'8([ g']) a'([ c'']) b'([ d'']) |
  <a' c''\pinceLR>2 << a'2\prall \\ <d' fis'\cadenceL>2 >>
  << b'2\pinceLR \\ g'4\pinceLR >>
}

left-right-ornaments.preview
rameau

Staff line avoidance

It would be better to vertically shift some ornamentations when the note head is on a staff line:

ornament-staff-line-wrong

But some other ornamentations, like pincés shall not be shifted. So a new shifted-when-on-line event property is used to tell whether an ornamentation grob shall be shifted or not when the note head is on a staff line:

trL =
#(make-music 'HeadOrnamentationEvent
   'shift-when-on-line #t   'left-text #{ \markup\rotate #45 \musicglyph #"scripts.stopped" #})

pinceR =
#(make-music 'HeadOrnamentationEvent
   'shift-when-on-line #f   'right-text #{ \markup \rotate #10 \fontsize #-3 \musicglyph #"scripts.rcomma" #})

The engraver “transfers” this event property to the grob:

#(define head-ornamentation-engraver
   (make-engraver
    (acknowledgers
     ((note-head-interface engraver note-grob source)
      ;; helper function to create HeadOrnamentation grobs
      (define (make-ornament-grob text direction shift-when-on-line)        (let ((ornament-grob (ly:engraver-make-grob engraver
                                                    'HeadOrnamentation
                                                    note-grob)))
          ;; use the given text as the grob text property
          (set! (ly:grob-property ornament-grob 'text) text)
          ;; set the grob direction (either LEFT or RIGHT)
          (set! (ly:grob-property ornament-grob 'direction) direction)
          ;; set the shift-when-on-line property using the given value
          (set! (ly:grob-property ornament-grob 'shift-when-on-line)
                shift-when-on-line)          (ly:pointer-group-interface::add-grob ornament-grob
                                                'elements
                                                note-grob)
          ;; the ornamentation is vertically aligned with the note head
          (set! (ly:grob-parent ornament-grob Y) note-grob)
          ;; compute its font size
          (set! (ly:grob-property ornament-grob 'font-size)
                (+ (ly:grob-property ornament-grob 'font-size 0.0)
                   (ly:grob-property note-grob 'font-size 0.0)))
          ornament-grob))
      ;; When the note-head event attached to the note-head grob has
      ;; ornamentation events among its articulations, then create a
      ;; HeadOrnamentation grob
      (for-each
       (lambda (articulation)
         (if (memq 'head-ornamentation-event
                   (ly:event-property articulation 'class))
             ;; this articulation is an ornamentation => make the grob
             ;; (either on LEFT or RIGHT direction)
             (begin
               (if (markup? (ly:event-property articulation 'left-text))
                   (make-ornament-grob
                     (ly:event-property articulation 'left-text)
                     LEFT
                     (ly:event-property articulation 'shift-when-on-line)))               (if (markup? (ly:event-property articulation 'right-text))
                   (make-ornament-grob
                     (ly:event-property articulation 'right-text)
                     RIGHT
                     (ly:event-property articulation 'shift-when-on-line))))))         (ly:event-property (ly:grob-property note-grob 'cause)
                            'articulations))))))

Finally, the ornamentation print function takes account of this property, to shift the ornamentation if required (the new shift-when-on-line property also needs to be defined):

#(define (head-ornamentation::print me)
   "Prints a HeadOrnamentation grob, on the left or right side of the
note head, depending on the grob direction."
   (let* ((notes (ly:grob-object me 'elements))
          (y-ref (ly:grob-common-refpoint-of-array me notes Y))
          (x-ref (ly:grob-common-refpoint-of-array me notes X))
          (x-ext (ly:relative-group-extent notes x-ref X))
          (y-ext (ly:relative-group-extent notes y-ref Y))
          (staff-position (ly:grob-staff-position (ly:grob-array-ref notes 0)))
          (y-coord (+ (interval-center y-ext)
                      (if (and (eqv? (ly:grob-property me 'shift-when-on-line)
                                     #t)
                               (memq staff-position '(-4 -2 0 2 4)))
                          0.5
                          0)))          (text (ly:text-interface::print me))
          (width (/ (interval-length (ly:stencil-extent text X)) 2.0))
          (x-coord (if (= (ly:grob-property me 'direction) LEFT)
                       (- (car x-ext) width)
                       (+ (cdr x-ext) width))))
     (ly:stencil-translate
      text
      (cons
       (- x-coord (ly:grob-relative-coordinate me x-ref X))
       (- y-coord (ly:grob-relative-coordinate me y-ref Y))))))

%% a new grob property (used to shift an ornamentation when the
%% note head is on a staff line)
#(define-grob-property 'shift-when-on-line boolean?
   "If true, then the ornamentation is vertically shifted when
the note head is on a staff line.")

We now have ornamentations that can be shifted when the note head is on a staff line (full example: shifted-ornaments.ly):

shifted-ornaments.preview

What’s next?

In the current implementation, ornamentations are drawn next to the note head, without considering the space they take, so collisions appear as soon as the note has an accidental or dots, or when notes are tightly spaced. In the next post, we’ll try to address this issue.

3 thoughts on “Adding ornamentations to note heads (part 2)

  1. Phil H

    Nicolas – this enhancement would fix Issue 1408, I think? Are you going to add the collision avoidance capability? If you can, I think it would make an excellent addition to the LilyPond main code base.

    Reply
  2. Nicolas Sceaux Post author

    Yes, that’s issue 1408 indeed. Part 3 of this series (ready to be published) deals with some collision issues, but only for a few specific cases; the proposed solution is just a hack that does not handle all situations. I confess that I do not understand how all this works in LilyPond.

    Reply
  3. Pingback: Adding Ornamentations to Note Heads (Part 3) | Scores of Beauty

Leave a Reply

Your email address will not be published. Required fields are marked *