Did you try attaching it to a mark?

You definitely set me on the right track there. Marks are typically only drawn on the top-most staff, and I didn't want to interfere with other marks, so my (slightly overkill) solution was to create a new engraver for this purpose. In any case, I learned some things about Lilypond internals.

%%%

\version "2.25.12"

#(define-event-class 'footnote-mark-event 'mark-event)

%% Shamelessly copied from Text_mark_engraver at scm/scheme-engravers.scm.
%% We shouldn't have more than one footnote per timestep per staff, but
%% I'm keeping that code to be safe.
#(define (Footnote_mark_engraver context)
  (let ((evs '())
        (grobs '()))
   (make-engraver
    (listeners
     ((footnote-mark-event engraver event)
      (set! evs (cons event evs))))
    ((process-music engraver)
     (for-each
      (lambda (ev)
       (let ((grob (ly:engraver-make-grob engraver 'TextMark ev))
             (offset (ly:event-property ev 'f-offset)))
        ;; Empty text
        (ly:grob-set-property! grob 'text (make-null-markup))
        ;; Attaching the footnote manually
        (ly:grob-set-property! grob 'footnote-music
         (once
          (propertyTweak 'annotation-line #f
           (make-music
            'FootnoteEvent
            'X-offset (car offset)
            'Y-offset (cdr offset)
            'automatically-numbered (ly:event-property ev 'f-auto)
            'text (ly:event-property ev 'f-mark)
            'footnote-text (ly:event-property ev 'f-footnote)))))
        (set! grobs (cons grob grobs))))
      evs))
    ((stop-translation-timestep engraver)
     (let ((staves (ly:context-property context 'stavesFound)))
      (for-each
       (lambda (grob)
        (ly:grob-set-object!
         grob
         'side-support-elements
         (ly:grob-list->grob-array staves)))
       grobs))
     (set! evs '())
     (set! grobs '())))))

#(ly:register-translator
  Footnote_mark_engraver 'Footnote_mark_engraver
  '((grobs-created . (TextMark)) ; Technically I don't create Footnotes
    (events-accepted . (footnote-mark-event))
    (properties-read . ())
    (properties-written . ())
    (description . "Create footnotes attached to invisible text marks.")))

%% I don't think there is any inherent difference between attaching the
%% footnote in this function as opposed to the engraver, except guaranteeing
%% that *any* FootnoteMarkEvent will have a footnote, even if not created
%% using this function.
m-footnote =
#(define-music-function (mark endmark offset content)
  (scheme? boolean? number-pair? markup?)
  (_i "Attach a footnote with content @var{content} to a text mark.

@var{mark} and @var{offset} are like their corresponding arguments in the
@code{footnote} function. @var{content} corresponds to @var{footnote} and
@var{endmark} decides whether to set this to a text mark or a text end mark.

The recommended @var{offset} for regular marks is @code{(1 . 3)}, and for end marks @code{(-1 . 3)} or @code{(-2 . 3)}.

You should probably use @code{markFootnote} or @code{endMarkFootnote}
instead.")
  (make-music 'FootnoteMarkEvent
   'f-mark (or mark (make-null-markup))
   'f-offset offset
   'f-footnote content
   'f-auto (not mark)
   ;; Properties are carried over when using ly:engraver-make-grob.
   'horizontal-direction (if endmark LEFT RIGHT)))

markFootnote =
#(define-music-function (mark offset content)
  ((markup?) number-pair? markup?)
  (_i "Attach a footnote to a text mark.")
  (m-footnote mark #f offset content))

endMarkFootnote =
#(define-music-function (mark offset content)
  ((markup?) number-pair? markup?)
  (_i "Attach a footnote to a text end mark.")
  (m-footnote mark #t offset content))

ossiaScore =
#(define-scheme-function (magnification music)
  (rational? ly:music?)
  #{
    \score {
      \new Staff \with {
        \magnifyStaff #magnification
        \omit TimeSignature % You can bring it back using \revert
      } { #music }
      \layout {
        ragged-bottom = ##t
        ragged-right = ##t
        ragged-last = ##t
        ragged-last-bottom = ##t
        indent = 0
      }
    }
  #})

ossia =
#(define-music-function (endmark offset magnification music)
  (boolean? number-pair? rational? ly:music?)
  (_i "Create a footnote with an ossia @var{music}.")
  (m-footnote #f endmark offset
   (markup
    #:italic "Ossia:"
    #:score (ossiaScore magnification music))))

markOssia =
#(define-music-function (offset magnification music)
  ((number-pair? '(1 . 3)) (rational? 2/3) ly:music?)
  (_i "Create a footnote with an ossia @var{music} attached to a text mark.")
  (ossia #f offset magnification music))

endMarkOssia =
#(define-music-function (offset magnification music)
  ((number-pair? '(-1 . 3)) (rational? 2/3) ly:music?)
  (_i "Create a footnote with an ossia @var{music} attached to an end mark.")
  (ossia #t offset magnification music))

%% Thank you <https://extending-lilypond.gitlab.io/en/extending/properties-types.html#new-music-type>
#(let
  ((define-event!
    (lambda (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<?)))))
  (define-event!
   'FootnoteMarkEvent
   '((description . "An invisible text mark used to attach footnotes.")
     (types . (footnote-mark-event event)))))

%%%

And here's an example usage:

%%%

\score {
  \relative d' {
    \time 2/2
    \key d \major
    fis4 4 e e |
    d2 a |
    fis'4 8 8 e4 e |
    d2 a |
    \markOssia \relative d' { \time 2/2 \key d \major b4 b cis a | d }
    b4 b cis cis |
    d fis e2 |
    d4 d e e |
    fis a e \repeat unfold 3 {
      fis |
      d2 a4 fis' |
      d2 r4
    }
    a'4 |
    a g fis e |
    d2.\fermata r4 \fine |
  }

  \layout {
    \context {
      \Staff
      \consists Footnote_mark_engraver
    }
  }
}

%%%

I still need to find some way to track footnote numbers, if that is even possible.

Reply via email to