Dear Jean,

as I've already said before, I'm grateful for all your efforts and your correction reply is yet another example: thank you! I made all the corrections and in fact the new grob has made some progress (attached)! I apologize for the naivety regarding the interfaces: I definitely misunderstood!

May I ask if you have any advice to better understand how all the "pieces" that make up Lilypond work?I don't know exactly where I intend to get with this study, but I think it will be really useful for me.

Thank you and happy new year to everyone!

Rip_mus

Il 31/12/2022 00:21, Jean Abou Samra ha scritto:
Le 30/12/2022 à 13:27, Rip _Mus a écrit :
Hello everyone,
for three days I have been immersed in the documentation written by Mr. Samra, on how to define a new grob, a new event and a new engraver that takes care of engraving the new grob, following a specific command.


Nice to hear :-)


Attached is the first result of my studies, available to everyone. Clearly the built grob is completely useless, but it helped me to make some attempts and understand the logic. I hope it could be a script that shows the various stages of defining new components, rather than modifying some existing ones. At present it needs a revision, so: to the more experienced, I would ask for an evaluation of what has been done (i'm not a programmer!), above all given the non-functioning of some overrides, which modify the properties of certain interfaces which are however included in the grob definition.


You seem to be expecting that adding some interfaces to the grob's
meta.interfaces list makes the grob support the properties declared by
the interfaces. It would be too easy if it worked like that :-) An
interface is not a magic wand that makes your grob support properties.
Rather, it serves to *document*, for human users (via the Internals
Reference manual), and for other parts of LilyPond's code (via
acknowledgers in engravers and the grob::has-interface function),
that your grob supports certain properties and behaviors. Most
often, the interface provides some callbacks that can be used
to actually give these behaviors to the grob. If you want your grob
to support the padding property, you need to give it a Y-offset callback
that reads this property, and side-position-interface provides
exactly such a one: side-position-interface::y-aligned-side .
Likewise, self-alignment-interface provides
ly:self-alignment-interface::aligned-on-x-parent (and some
variants) which you should put into X-offset so that PippoText
supports self-alignment-X and parent-alignment-X.

If you set Y-offset to ly:side-position-interface:y-aligned-side,
you will see that it outputs programming errors.  The reason is that
you read Y-offset in process-music in the engraver, which triggers
all the positioning logic, which reads X-extent and Y-extent while
they're still empty at that point. Did you leave the y-offset variable
by accident, since it's unused?

In general, it's a better idea to make something computed by
a callback than to ly:grob-set-property! it. In this case,
you can remove all this part of the code in the engraver

              (t (ly:grob-property grob 'text))
              (stil (grob-interpret-markup grob t))
              (x-extent (ly:stencil-extent stil X))
              (y-extent (ly:stencil-extent stil Y))
              (y-offset (ly:grob-property grob 'Y-offset))
...
            (ly:grob-set-property! grob 'X-extent x-extent)
            (ly:grob-set-property! grob 'Y-extent y-extent)

and instead use in the grob definition

  (X-extent . ,ly:grob::stencil-width)
  (Y-extent . ,ly:grob::stencil-height)

Those two functions are equivalent to

(define (ly:grob::stencil-width grob)
  (let ((stencil (ly:grob-property grob 'stencil)))
    (if (stencil? stencil)
        (ly:stencil-extent stencil X)
        empty-interval)))

(define (ly:grob::stencil-height grob)
  (let ((stencil (ly:grob-property grob 'stencil)))
    (if (stencil? stencil)
        (ly:stencil-extent stencil Y)
        empty-interval)))

Actually, you can even leave out that part of the grob
definition entirely, because using ly:grob::stencil-width
and ly:grob::stencil-height is the default.

Lastly, your function pippo-stencil

#(define (pippo-stencil grob) (grob-interpret-markup grob (ly:grob-property grob 'text)))

is the same as ly:text-interface::print.


HTH,
Jean
\version "2.24.0"
%-----------------------------------------------------------------
% Pippo è un oggetto post evento che, una volta chiamato (\pippo), stampa la stringa "pippo" di un proprio colore
% (rosso di default) e rende la testa della nota relativa dello stesso colore.
% Di seguito:
% - le dichiarazioni delle funzioni di base per definire un grob e un evento (grazie a Jean Abou Samra!);
% - definizione di una nuova interfaccia, un nuovo stencil, un nuovo grob (con relativa dichiarazione #all-grob-descriptions);
% - definizione della classe di eventi, dell'evento stesso, di una funzione di creazione e di un comando Lilypond;
% - definizione dell'engraver, con relativo inserimento nel contesto Voice.
% Infine un esempio.
% [le intestazioni delle varie sezioni sono in italiano]
% Questo script è a puro scopo di studio e chiaramente non serve a nulla!
% Può essere uno spunto per costruire degli oggetti personalizzati ed estendere le funzionalità di Lilypond
%-----------------------------------------------------------------
% Pippo (Foo) is a post-event object which, once called (\pippo), prints the string "pippo" in its own color (red by default)
% and makes the relative note head of the same color.
% Right away:
% - the basic function declarations to define a grob and an event (thanks to Jean Abou Samra!);
% - definition of a new interface, a new stencil, a new grob (with relative declaration #all-grob-descriptions);
% - definition of the event class, the event itself, a create function and a Lilypond command;
% - definition of the engraver, with relative insertion in the Voice context.
% Finally an example.
% [the headings of the various sections are in Italian]
% This script is for study purposes only and is clearly useless!
% It can be a starting point for building custom objects and extending the functionality of Lilypond
%-----------------------------------------------------------------
%--------------FUNZIONI DI BASE-----------------------------------

#(define (define-grob! grob-name grob-entry)
   (set! all-grob-descriptions
         (cons ((@@ (lily) completize-grob-entry)
                (cons grob-name grob-entry))
           all-grob-descriptions)))

#(define (define-event! type properties)
   (set-object-property! type
     'music-description
     (cdr (assq 'description properties)))
   (set! properties (assoc-set! properties 'name type))
   (set! properties (assq-remove! properties 'description))
   (hashq-set! music-name-to-property-table type properties)
   (set! music-descriptions
         (sort (cons (cons type properties)
                 music-descriptions)
           alist<?)))


%--------------DEFINIZIONE DEL GROB--------------------------------

#(ly:add-interface
  'pippo-interface
  "Just a markup with pippo"
  '()
  )

%#(define (pippo-stencil grob) (grob-interpret-markup grob (ly:grob-property grob 'text)))

#(define-grob!
  'PippoText
  `(
     (stencil . ,ly:text-interface::print)
     (X-extent . ,ly:grob::stencil-width)
     (Y-extent . ,ly:grob::stencil-height)
     (X-offset . ,ly:self-alignment-interface::aligned-on-x-parent)
     (Y-offset . ,ly:side-position-interface::y-aligned-side)
     (padding . 0)
     (text . "pippo")
     (color . ,red)
     (layer . 0)
     (font-size . 0)
     (font-series . bold)
     (transparent . #f)
     (outside-staff-priority . 100)
     (direction . ,UP)
     (meta . (
               (class . Item)
               (interfaces . (
                               pippo-interface
                               text-interface
                               text-script-interface
                               font-interface
                               self-alignment-interface
                               item-interface
                               grob-interface
                               )
                 )
               )
       )
     )
  )

\layout {
  \context {
    \Global
    \grobdescriptions #all-grob-descriptions
  }
}

%--------------DEFINIZIONE E CREAZIONE DEL NUOVO EVENTO------------
%------------------------------------------------------------------

#(define-event-class 'pippo-event 'music-event)

#(define-event!
  'PippoEvent
  '(
     (description . "Used to trigger pippo")
     (types . (post-event pippo-event event))
     )
  )

#(define (make-pippo-script)
   (make-music 'PippoEvent 'direction UP 'padding 100))

pippo = #(make-pippo-script)

%--------------DEFINIZIONE DELL'ENGRAVER---------------------------

#(define (Pippo_engraver context)
   (let
    (
      (pippo-event #f)
      (note-heads '())
      (glob-col '())
      )
    (make-engraver
     (listeners
      (
        (pippo-event engraver event)
        (set! pippo-event event)
        )
       )
     (
       (process-music engraver)
       (if pippo-event
           (let*
            (
              (grob (ly:engraver-make-grob engraver 'PippoText pippo-event))
              (dir (ly:event-property pippo-event 'direction))
              (PCgrob (ly:context-property context 'currentMusicalColumn))
              )
            (set! glob-col (ly:grob-property grob 'color))
            (ly:grob-set-parent! grob X PCgrob)
            (ly:grob-set-property! grob 'direction (if dir dir UP))
            grob
            )
           )
       )
     (acknowledgers
      (
        (note-head-interface engraver grob source-engraver)
        (set! note-heads (cons grob note-heads))
        )
      )
     (
       (process-acknowledged engraver)
       (for-each
        (lambda (note-head)
          (if (and note-head pippo-event)
              (begin
               (ly:grob-set-property! note-head 'color glob-col)
               )
              )
          )
        note-heads)
       (set! note-heads '())
       )
     (
       (stop-translation-timestep engraver)
       (set! pippo-event #f)
       (set! glob-col '())
       )
     )
    )
   )

\layout {
  \context {
    \Voice
    \consists #Pippo_engraver
  }
}

%----------------------------------------------------------------------------

\relative {
  \once \override PippoText.font-size = 3
  \once \override PippoText.padding = 10
  a8_\pippo b4 e8 f2 r
  \once \override PippoText.direction = #DOWN
  \once \override PippoText.color = #blue
  \once \override PippoText.whiteout = ##t
  \once \override PippoText.layer = 2
  \once \override PippoText.extra-offset = #'(0 . -2)
  \once \override PippoText.self-alignment-X = 1
  <g, d' a'>8\pippo a r e' g2
  \once \override PippoText.parenthesized = ##t
  \once \override PippoText.show-vertical-skylines = ##t
  \once \override PippoText.show-horizontal-skylines = ##t
  a2_\pippo
}

Reply via email to