Partially Compiling a LilyPond Score

Partially recompiling a score has been a major LilyPond feature request for quite some time. I hope I have now found a promising path towards that goal, and today I’m going to present a first working version of that function. For this I’m heavily relying on Jan-Peter Voigt’s work on lalily and his Music Tree ideas. But fortunately what I’m proposing is very simple to use, without having to dive into the (still) complex world of the edition-engraver.

Injecting Stuff Into LilyPond Scores

The edition-engraver is a function taken from Jan-Peter’s lalily and integrated in openLilyLib. Its key feature is the possibility to “inject” elements into a score by assigning them to a voice, measure and measure-position. This makes it possible to define tweaks or additional elements in separate files and maintain the content separately from the presentational aspect. \shape commands to tweak slurs, manual breaks etc. don’t have to be mixed with the music anymore. Maybe the most important thing is that you can target different “editions”, e.g. the full score or parts, so this more or less makes the use of \tags obsolete.
I won’t go into more details about it today because the edition-engraver itself is not yet ready for general use or even inclusion into LilyPond. But as it is part of openLilyLib you can have a look at it on GitHub. Today I’m concentrating on a single use case and on presenting a new function I just added to openLilyLib.

Partially Compiling a LilyPond Score — The Manual Way

The LilyPond documentation explains how to save compilation (i.e. waiting) time by skipping music that is of no current interest. This can be controlled by the Score’s skipTypesetting property which can switch typesetting on and off at will. By inserting three of these switches in a music expression you can effectively clip a portion of the score for partial compilation:

{
  % Switch typesetting off at the start of the score
  \set Score.skipTypesetting = #t
  % some music that is currently uninteresting

  % Switch typesetting on
  \set Score.skipTypesetting = #f
  % The music we want to see

  % Switch typesetting off again
  \set Score.skipTypesetting = #t
}

While this is an efficient method to speed up compilation it is not really maintainable. You have to take care of all three commands, which is inconvenient and error-prone, especially when you have set up your score in multiple files. And you don’t want to have these entries affect the commits in the version control, which adds extra maintenance work and concentration. So I always wished there was a convenient automatic solution to make this approach more practical. But while my ideas had centered around preprocessing the input files with scripts I now realized that the edition-engraver is the tool of choice for the task.

Use the Edition Engraver for Partial Compilation

The edition-engraver is able to inject arbitrary skipTypesetting commands into a score like this:

\editionMod 
  clips 
  1 0/4 
  clip-regions.Score.A
  \set Score.skipTypesetting = ##t
  • \editionMod is the command to inject a single “mod”
  • clips is the “target”. Typical targets are score or parts, but I have defined the target clips for the purpose of this function
  • 1 0/4 denotes the first beat in the first measure (counting of the position in the measure is (so far?) zero-based because it’s not about “beats” but about the fraction of the time).
  • clip-regions.Score.A refers to the first (“A”) Score context in the clip-regions music tree (as described in Jan-Peter’s referenced posts).
  • finally \set Score.skipTypesetting = ##t is the command to be injected, telling LilyPond not to engrave the music starting at that point.

Using this I was able to wrap this in a Scheme function that takes two bar numbers as arguments and injects the mentioned three skipTypesetting commands at the appropriate points in the score. This function is now part of openLilyLib, and — provided you have “installed” that properly — you can simply write

\include "general-tools/clip-regions/definitions.ily"
\setClipRegion 34 49

to let LilyPond compile only measures 34 through 49 of the current score. Practically the function hides away the complexity of setting up the edition-engraver, and these two lines are really everything you need. Now this is actually maintainable while working on a score: If your focus moves to another section all you have to do is change the arguments to that function, or you can simply comment it out when you want to compile the whole score.

Once this was up and running I added another layer of comfort to this. When you are recreating an existing score (be it a hand-written draft or a historic edition) you are often working in the context of the original page layout. While entering and proofing music it makes sense to have LilyPond respect the original breaks because that significantly eases your navigation between the model and the score engraved by LilyPond. To support this workflow I added the functions \setClipPage and \setClipPageRange to let LilyPond compile a single page or a range of pages:

\include "general-tools/clip-regions/definitions.ily"
originalPageBreaks = #'(11 22 28 35 48 60)
% Comment out one of the following lines
\setClipPage 4
\setClipPageRange 1 3

Of course LilyPond doesn’t know by herself about the original page breaks, so the functions rely on the presence of a list originalPageBreaks that you have to provide. This is a simple Scheme list containing the bar numbers of all page breaks. So in the above example the “11” refers to first barnumber on page 2, and it would compile measures 28–34 (page 4) or 1–27 (pages 1–3) of the score.

If you want to play around with the new function you can copy & paste the following example file and uncomment the different \setClip... lines to see their effect. This will also show you some validity checks built into the functions:

\version "2.18.0"

\include "general-tools/clip-regions/definitions.ily"

% Define a list with original page breaks (barnumbers)
originalPageBreaks = #'(112 224 336 448 560 672 784)

% Define a region with barnumbers
%\setClipRegion 8 25

% Compile a single page
%\setClipPage 5

% Compile a page range
%\setClipPageRange 4 5

% Negative page range triggers a warning
%\setClipPageRange 5 3

% Non-existent pages result in errors
%\setClipPageRange 3 123
%\setClipPage -2

music = \relative c' {
  \repeat unfold 800 {
    c d e d
  }
}

\score {
  \new Staff \music
}

[Edit] If you want to inspect the code of these new functions you can do so here. Considering their power this is remarkably simple Scheme code — which is due to the fact that the real complexity is hidden away behind the \include "editorial-tools/edition-engraver/definitions.ily".

Quo Vadis?

I think I will never again work without this on a score that takes a noticeable time to compile (which is about anything beyond basic music examples), and I hope this can be a useful addition for everybody else’s toolbox. But I hope this approach has a considerably more powerful potential: I think I see a way how this functionality can be integrated in the Frescobaldi IDE. It will allow you to define the range of the score that is compiled so you only need to wait for the engraving of the portion that actually interests you. The ultimate goal is to make Frescobaldi automatically recompile the system you are currently editing — which will bring the responsiveness of editing LilyPond scores to a new Level!

And once we have this we can think of anything else the edition-engraver can do for us to create fancy new editing/compilation features for Frescobaldi ­čÖé .

2 thoughts on “Partially Compiling a LilyPond Score

Leave a Reply

Your email address will not be published. Required fields are marked *