Time mark engraver
When working with LilyPond’s MIDI output it can be tedious to find the exact ‘elapsed time from the start of the score to an arbitrary point’ – especially when the music contains multiple tempo changes. Counting measures and doing tempo math is error-prone; inspecting the MIDI file manually is possible but time consuming.
This snippets introduces a Scheme engraver that allows you to insert time marks directly into the music. At any point in the score you can add \timeMark, and LilyPond computes the elapsed time up to that position and displays it (for example, ‘2m15s’).
This is useful when you need to
- align score events with audio or video cues,
- locate timestamps for MIDI-driven playback or mockups,
- find “time since start” at a few musical key positions without counting or math, or
- work reliably across complex tempo maps (rit., accel., metric modulations, etc.).
Upgrade notice: This snippet must be updated with the convert-ly script if used with a LilyPond version newer than 2.24.
\version "2.24"
% LSR source: https://lists.gnu.org/archive/html/lilypond-user/2024-03/msg00127.html
% LSR credits: 2024 - Jean Abou Samra
#(define (Elapsed_time_engraver context)
(define (format-time seconds) ; example: 2m15s
(let ((minutes (euclidean-quotient seconds 60))
(rest (euclidean-remainder seconds 60)))
(string-append (if (zero? minutes) "" (format #f "~am" minutes))
(format #f "~as" (round rest)))))
(let ((wholes-per-minute 15)
(last-time ZERO-MOMENT)
(total-time 0)
(marks '()))
(make-engraver
((process-music engraver)
(let* ((new-time (ly:context-current-moment context))
(time-delta (ly:moment-main
(ly:moment-sub new-time last-time)))
(new-wholes-per-minute
(and=> (ly:context-property context
'tempoWholesPerMinute #f)
ly:moment-main)))
(set! total-time
(+ total-time (* 60 (/ time-delta wholes-per-minute))))
(set! last-time new-time)
(when new-wholes-per-minute
(set! wholes-per-minute new-wholes-per-minute))))
(acknowledgers
((text-mark-interface engraver grob source-engraver)
(set! marks (cons grob marks))))
((process-acknowledged engraver)
(for-each (lambda (grob)
(when (assq-ref (ly:grob-property grob 'details)
'time-mark)
(ly:grob-set-property!
grob 'text (format-time total-time))))
marks)
(set! marks '())))))
\layout {
\context {
\Score
\consists #Elapsed_time_engraver
}
}
% The argument of the `\textEndMark` here is just a placeholder
% that the engraver replaces with a time stamp (because the
% `time-mark` subproperty is set).
timeMark = \tweak details.time-mark ##t
\tweak color "red"
\textEndMark ""
{
c'1 | \timeMark
\tempo 4 = 120 c'4 8. 16 2 | \timeMark
\tempo 4 = 180 \repeat unfold 4 c'2 | \timeMark
\repeat unfold 180 c'4 | \timeMark
}