LilyPond Files as Functions
Richard Davis, Blog

Home About Events Listening Blog FR


I'm very gradually switching to LilyPond as my main notation software. As part of this, I'm trying to optimize my workflow as much as possible so as to minimize overall pain. One way to do so is with scheme music functions, which enable the programmer to substitute arguments into a music expression. This is incredibly powerful, but it does have its limitations, the main one being that it can only return music, not a score or something like that.

That's where this post comes in. I've developed a fun workaround that allows for calling a function-like object and getting back something that is not strictly music.


This workaround uses the \include functionality to treat a file like a function. Files on their own don't take parameters, but it is possible to make them behave like they do. The way to do so is by including variable substitutions and function calls (\var or \func) that are undefined in the function file. To pass parameters to the file, simply define those variables or functions before "calling" (or \include-ing) the file. These parameters can easily be redefined and the file can be called again with new values.


When working with multi-movement pieces, which have a \score block for each movement, the \score is the most pressing thing to have a function for, and it is the reason I developed this workaround. In a piece, I'm working on right now, the score for each movement looks something like this:

\include "./common.ily"

\score {
  \header {
    piece = \markup \movement-title \piece % piece undefined
    tagline = ""

  % partStaff defined in common.ily
  \partStaff \instrName \shortInstrName \notes % all args undefined in this line

  % my layout preferences
  \layout {
    \context { \Staff \override = ##f }
    \context {
      \override Glissando.minimum-length = 4
      \override Glissando.springs-and-rods = #ly:spanner::set-spacing-rods
      \override Glissando.thickness = 2
      \override SpacingSpanner.shortest-duration-space = 4.0

Repeating this for every movement would be tedious, and if I had to change anything (e.g. a layout setting) I would have to do so for every one of seven movements. This becomes unmaintainable quickly. Instead, I do as described above, and \include this file like a function for when I need a score:

% normal header
\version "2.24.3"
\language "english"

\include "../common/common.ily"
% defs.ily defines all of the arguments the movementScore "function"
\include "./defs.ily"

% include the toplevel header for things like title, subtitle, composer, and
% tagline
\include "../book-header.ily"
% call the function!
\include "../common/movementScore.ily"

And we're done! Excluding comments and blank lines, the score for each movement is six lines long, and is identical between movements.


In developing an engraving workflow in LilyPond, I imagine the most important area to focus my efforts will be these creative ways to minimize repetition and to streamline the writing process. Someday I'll hopefully have it down to a science, but in the meantime, if any readers have any tips or tricks they'd like to share, I'd love to hear about them via email at!