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.