Introduction to extending

LilyPond uses the Guile implementation of the Scheme programming language as an extension language. While the bulk of LilyPond’s core is written in C++ for performance, Guile’s interfaces are used heavily, and a surprising number of functions is exported to Scheme, making it accessible to user extensions. This is one of the keys to LilyPond’s exceptional flexibility.

The version of Guile embedded in LilyPond is currently Guile 1.8 (not the latest Guile 3.0).

Inserting Scheme inside LilyPond

To start a Scheme expression in a LilyPond file, prefix it with a hash character.

myVar = #(+ 2 2)

\repeat unfold #myVar { c } % This note, 4 times.

This example also shows that LilyPond-defined variables are accessible from Scheme. The reverse holds too:

#(define mySecondVar 42)

\repeat unfold \mySecondVar { c }

The #@ and $@ operators insert all elements from a Scheme list in the LilyPond context. They are called list splicing operators.

Inside Scheme, one can even write values in LilyPond syntax by enclosing them inside #{ and #}.

Here is an example using both list splicing and embedded LilyPond:

notes = #(list #{ c'4 #}
               #{ e'4 #}
               #{ g'4 #}
               #{ c''4 #})

{
  % All notes from the list in a sequential expression.
  #@notes
  % Same, in simultaneous music.
  << #@notes >>
}

Exported functions and naming

Two kinds of functions are available inside LilyPond. Guile defines a large number of general-purpose data utilities to process strings, lists, etc. And LilyPond itself adds a layer on top by providing functions manipulating its own object types like music, contexts or markups.

Many functions exported by LilyPond have their names start with the ly: prefix, to distinguish them from native Scheme functions, for example: ly:music-length. This is done systematically for functions exported from C++. Additionally, some parts of LilyPond’s Scheme code export functions named with leading ly:. Some others do not, however. Hopefully consistency could be brought in the future.

The Scheme sandbox

To experiment with Scheme in the context of LilyPond, run the terminal command:

lilypond scheme-sandbox

This opens an interactive Guile prompt where all functions exported by LilyPond are available.

Music function primer

Many of the building blocks encountered in LilyPond input are music functions: \relative, \transpose, \bar, etc. You can write your own, too. They are declared using define-music-function, and a type predicate has to be picked for every argument.

simpleAccompaniment =
#(define-music-function (note1 note2 note3)
                        (ly:music? ly:music? ly:music?)
   #{
      \repeat unfold 2 {
        #note1 #note2 #note3 #note2 #note3 #note2
      }
   #})

{
  \time 6/4
  \simpleAccompaniment c'8 g' e''
  \simpleAccompaniment c'8 g' f''
  \simpleAccompaniment d'8 g' f''
  \simpleAccompaniment c'8 g' e''
}

Here are the most common type predicates:

Predicate name

Accepted values

number?

Any numeric value: integer, floating point number, fraction

integer?

An integer

index?

A non-negative integer

string?

A string (sequence of characters, like "Some words")

markup?

A \markup block; this also accepts strings

boolean?

A boolean, true (#t) or false (#f)

ly:music?

A music expression

ly:pitch?

A pitch (like the first two arguments to \transpose c des { ... })

ly:duration?

A duration (like the optional argument to \tuplet: \tuplet 3/2 8. { ... })

color?

A color, given as string or RGB list (documentation)

list?

A list of any elements

Callback primer

Grobs, short for “Graphical Objects”, are pieces of notation – note heads, stems, rests, etc. They are familiar to LilyPonders, who are used to the syntax of \override to modify grob properties.

Instead of setting the value in stone, any grob property can be set to a callback, a function that calculates the property on-the-fly (see Glossary of important object types). Other properties are retrieved using ly:grob-property. Here is a function that draws feathered beams in the direction where they go up: right is an ascending beam, left if a descending beam. The notational effect is that the score indicates to accelerate whenever the melody goes up – probably to the distaste of a music teacher.

#(define (grow-in-up-direction beam)
   (let* ((Y-positions (ly:grob-property beam 'positions))
          (left-position (car Y-positions))
          (right-position (cdr Y-positions)))
     (cond
       ((< left-position right-position)
        RIGHT)
       ((> left-position right-position)
        LEFT)
       (else
        CENTER))))

\relative c' {
  \override Beam.grow-direction = #grow-in-up-direction
  c'16 d e f g f e d c g c g c e g c
}

Printing values

The standard Scheme procedure display is ill-suited to debugging messages in the context of LilyPond, because it prints on the standard output stream stdout, while LilyPond’s own logging messages go to stderr, which can cause them to appear intermingled. It is handier to use ly:message. Unlike display, its first argument must be a string. Extra arguments are used to format the message according to the standard format specifiers, as with the format function. Another difference is that ly:message prints a new line automatically.

For example, here is how you might print the content of the Y-positions variable in the callback example above:

(ly:message "Y-positions are: ~a" Y-positions)

The most important format specifiers are ~a and ~s. The former prints values in the prettiest way like display, while the latter tries to make them loadable like write. In the case of strings, this means that ~s prints quotes, while ~a does not.

The ~y formatter attemps to print nested data structures in a readable way. It is only available with the standard procedure format:

{
  \override Slur.after-line-breaking =
    #(lambda (grob)
       (ly:message
         (format #f "~y" (ly:grob-property grob 'control-points))))
  b'1( b'')
}

This example outputs:

((0.732364943841144 . 1.195004)
 (1.76258769418946 . 3.05939544438528)
 (6.83883925432087 . 5.14594080151585)
 (8.88241194297576 . 4.545004))

First steps with the internals

Smobs

“Smob” means “Scheme Object”. Smob types are exported from LilyPond’s C++ core to Scheme, along with functions to operate on them. For example, pitches are Smobs. As with all Smob types, a predicate is provided for them: ly:pitch?. As with many Smob types, you also get a constructor for building pitches: ly:make-pitch. There are various utilities as well: ly:pitch-notename, ly:pitch-transpose, and others.

Probs

“Prob” is short for “Property Object”. In LilyPond, this term encompasses a wide range of objects that hold properties. All Probs are smobs, but the contrary is not true. Pitches and durations, for example, are not Probs – they have no concept of properties. On the other hand, music objects or contexts are Probs.

For all common Prob types, functions are provided to retrieve and set properties. They all follow the same scheme for naming and signatures.

(ly:<something>-property object property [default])

Access a property in a Prob. <something> here stands for the prob type. There are ly:music-property, ly:event-property, ly:context-property, etc.

The property name is given as a symbol. To whet the reader’s appetite about music objects, the following example can be tested:

#(display (ly:music-property #{ c'8 #} 'pitch))

When the Prob has no such property, default is returned, if provided, or else, the empty list. Note that contrary to most standard Scheme functions, prob property accessors fall back to the empty list, not the boolean false!

(ly:<something>-set-property! object property value)

Set property in object to value.

This corresponds to ly:music-set-property!, ly:event-set-property!, etc.

Glossary of important object types

The following objects play a salient role in the grand scheme of LilyPond.

Output definitions

\layout, \midi and \paper blocks. They are the architects of the process from input to output. They contain settings for context defaults and page spacing as well as line breaking parameters: indent, page-breaking, system-system-spacing, \context { \Staff \override ... }.

Books, bookparts and score

\book, \bookpart and \score blocks. Books are the top-level containers, corresponding to one output file. They can contain scores and bookparts. Scores are the unit for music typesetting, they correspond to the different pieces. Bookparts are an optional intermediary level between books and scores.

Each of these objects contains an output definition, which is inherited: settings made in a \layout block inside \score overwrite those made inside the enclosing \bookpart, those inside \bookpart overwrite those in \book, and the latter ones have priority over output definitions on the top level.

Music

Music objects are a nested, tree-like representation of the musical content described by the input. For instance, << c'8 \\ d16\p >> is a piece of simultaneous music (SimultaneousMusic) containing two music expressions. The second one is a note (NoteEvent), in turn containing a dynamic (AbsoluteDynamicEvent).

Contexts

Property containers corresponding to different parts of the score. The most common context types are Score, Staff and Voice. They are used for data sharing, and their hierarchy plays a role in the operation of translators.

Iterators

Objects, residing in contexts, that advance in the music in a certain way. For example, the Sequential_iterator advances step by step in its music. On the other hand, the Simultaneous_music_iterator does a “crab-walk” to advance in several music expressions in parallel.

Translators

Multi-faceted objects, also residing in contexts, tasked with turning the events into graphical objects and setting the relationships between these objects. There are two kinds of translators: engravers, for graphical output, and performers, for outputting MIDI.

Grobs

The short name for “Graphical Objects”. They represent a piece of notation on the page: a note head (NoteHead), a stem (Stem), a dot (Dots), etc.

Callbacks

Grobs have properties. A callback allows a grob property to be computed from other properties of this grob and related grobs (e.g., the stem associated to a note head). A callback is simply a procedure taking the grob as argument, and returning the value for the property. An example is found in Callback primer.

Stencils

Inkings ready to be output. Nearly every grob has a stencil, which ends up on the page as a bit of notation. Stems produce line stencils, for instance.

Markups

Code-like objects that can be interpreted into stencils. Many grobs use them to prepare their stencil. They may be inserted in the musical input as textual articulations, as in { c'8^\markup \italic { Cresc. poco a poco } }. They can also be placed on the top level.

Overview of LilyPond’s inner workings

LilyPond translates text input to music scores. This is a complex process that happens in several steps.

Parsing

The first stage is lexical analysis of the input file. The tokenizer operates first. Its action is to turn the file into a stream of tokens, for easier processing by the parser. The tokenizer is written in Flex.

The main parser is written in Bison. It understands the syntax, and produces various kinds of objects for processing by later steps: output definitions, books, bookparts, scores and music expressions as well as markups.

During parsing, music expressions are transformed by music functions, like \relative or \transpose. The ability to define custom music functions is the source of a large part of LilyPond’s extending powers.

Iterating

The step of iterating happens for every \score block. The purpose is to translate the stream of music events into a network of grobs. For example, a note event in the input is reflected by a NoteHead, a Stem, maybe a Beam, Dots, etc. Hence this step is also called translation.

This is where iterators and contexts are created. They contain engravers, which are responsible for creating grobs. In MIDI, engravers are replaced with performers, and which make MIDI objects.

Custom context types can be defined in output definitions. Engravers can be written in Scheme. Iterators and performers are C++-only.

Pure positioning

What we get from translation is a set of interconnected objects. Their positioning is going to depend heavily on the chosen line breaking. If things were simple, the next step would be to find a line breaking configuration.

But things are not as simple, since the space that an object might take also depends on the line breaking.

LilyPond’s solution to this problem is to do a preliminary positioning pass, called the pure phase. The extents and offsets of every object are estimated, making best guesses, for use by the line breaker and the later unpure phase.

You can write your own pure functions in Scheme.

Page breaking and line breaking

Breaking algorithms try to fit the music to the page as evenly as possible. Some also try to minimize other kinds of constraints, like the page turn breaking algorithm.

This part is not Scheme-accessible.

Unpure positioning

When information about the line breaking configuration is available, all objects can be placed precisely, and their stencils (“inkings”) are built. This is completely transparent to Scheme.

Writing output

In the end, the stencils are translated into PostScript language. Behind the scenes, this is converted by GhostScript into a PDF file.