2.8 Unpure-pure containers

Note: While this subsubsection reflects the current state, the given example is moot and does not show any effect.

Unpure-pure containers are useful for overriding Y-axis spacing calculations – specifically Y-offset and Y-extent – with a Scheme function instead of a literal (i.e., a number or pair).

For certain grobs, the Y-extent is based on the stencil property, overriding the stencil property of one of these will require an additional Y-extent override with an unpure-pure container. When a function overrides a Y-offset and/or Y-extent it is assumed that this will trigger line breaking calculations too early during compilation. So the function is not evaluated at all (usually returning a value of ‘0’ or ‘'(0 . 0)’) which can result in collisions. A ‘pure’ function will not affect properties, objects or grob suicides and therefore will always have its Y-axis-related evaluated correctly.

Currently, there are about thirty functions that are already considered ‘pure’ and Unpure-pure containers are a way to set functions not on this list as ‘pure’. The ‘pure’ function is evaluated before any line-breaking and so the horizontal spacing can be adjusted ‘in time’. The ‘unpure’ function is then evaluated after line breaking.

Note: As it is difficult to always know which functions are on this list we recommend that any ‘pure’ functions you create do not use Beam or VerticalAlignment grobs.

An unpure-pure container is constructed as follows;

(ly:make-unpure-pure-container f0 f1)

where f0 is a function taking n arguments (n >= 1) and the first argument must always be the grob. This is the function that gives the actual result. f1 is the function being labeled as ‘pure’ that takes n + 2 arguments. Again, the first argument must always still be the grob but the second and third are ‘start’ and ‘end’ arguments.

start and end are, for all intents and purposes, dummy values that only matter for Spanners (i.e Hairpin or Beam), that can return different height estimations based on a starting and ending column.

The rest are the other arguments to the first function (which may be none if n = 1).

The results of the second function are used as an approximation of the value needed which is then used by the first function to get the real value which is then used for fine-tuning much later during the spacing process.

#(define (square-line-circle-space grob)
(let* ((pitch (ly:event-property (ly:grob-property grob 'cause)
                                 'pitch))
      (notename (ly:pitch-notename pitch)))
 (if (= 0 (modulo notename 2))
     (make-circle-stencil 0.5 0.0 #t)
     (make-filled-box-stencil '(0 . 1.0)
                              '(-0.5 . 0.5)))))

squareLineCircleSpace = {
  \override NoteHead.stencil = #square-line-circle-space
}

smartSquareLineCircleSpace = {
  \squareLineCircleSpace
  \override NoteHead.Y-extent =
   #(ly:make-unpure-pure-container
      ly:grob::stencil-height
      (lambda (grob start end) (ly:grob::stencil-height grob)))
}

\new Voice \with { \remove Stem_engraver }
\relative c'' {
  \squareLineCircleSpace
  cis4 ces disis d
  \smartSquareLineCircleSpace
  cis4 ces disis d
}

[image of music]

In the first measure, without the unpure-pure container, the spacing engine does not know the width of the note head and lets it collide with the accidentals. In the second measure, with unpure-pure containers, the spacing engine knows the width of the note heads and avoids the collision by lengthening the line accordingly.

Usually for simple calculations nearly-identical functions for both the ‘unpure’ and ‘pure’ parts can be used, by only changing the number of arguments passed to, and the scope of, the function. This use case is frequent enough that ly:make-unpure-pure-container constructs such a second function by default when called with only one function argument.

Note: If a function is labeled as ‘pure’ and it turns out not to be, the results can be unexpected.


Extending LilyPond v2.25.20 (development-branch).