Trees, Music and Lilypond

In this post I write about some hierarchical aspects of typesetting music with LilyPond. In a way the elements that form a musical score can be split into pieces that stand in a hierarchical relation. We can make use of this relationship to do interesting stuff such as creating templates and injecting tweaks into a score without touching the music source itself. For my personal use I have developed several tools that help me manage my projects in a structured manner. Recently I made them publicly available and today I’ll start introducing them on this blog. This first post is about the theory behind the implementation which I will describe in following articles.

Compositing Music

Any piece of music can be seen as a tree — I might say: It is alive 😉 . But here I want to look at the way of compositing music. Yes, it is not composing, but assembling parts to a complete piece of engraved music. A piece of music can be seen as a tree with branches from several perspectives. If you look at the printed sheet music, you will find a conductor having a full score on his desk (or in his head) while the individual instrumentalist or singer only deals with the necessary information for performing his own part. The conductor is looking at the whole tree, a single particpant is looking at a single branch.

Now what does this have to do with typesetting music? Let us first look at a very simple score and dissect it.

Dissecting Music

If you enter the music in a lilypond file, you will most likely define variables for every participating musician and then use them in a score block to actually engrave the piece. The names for the variables might for example be:

violin = \relative c'' { a4 b c d }
viola = \relative c' { r8 f e4 g f }
cello = \relative c { c2 bes }

\score {
  <<
    \new Staff { \violin }
    \new Staff { \clef "alto" \viola }
    \new Staff { \clef "bass" \cello }
  >>
}

This is an easy case: We just have to combine three staves to compose the full score and use the variables for each part — making this an example of the mentioned situation with a conductor and the instrumentalists. Vocal music adds a level of complexity already: each singer needs not only a melody but also lyrics. The variables might be called sopranoMelody, sopranoLyrics, altoMelody and so on. So the singers are looking at two sub-branches – melody and lyrics – for their own voice. This example exposes a strict naming scheme, marking the hierarchy with an upper case letter in the variable name.

Adhering to such a naming scheme makes it possible to go one step further: Instead of using the music variables directly we can create a (Scheme) function that automatically retrieves the right music variables by selecting them based on a given part of the variable name (you will see later what that means). This way you may not have to type all variables and contexts explicitly. And it will help us circumvent another issue you have to take care of when writing everything explicitly: how to arrange the definition of music variables and score blocks when combining multiple movements to one piece. You can find working solutions on the net, for example in the lilypond-user archive. Rainhold Kainhofers OrchestralLily also deals with this issue, but in this post I want to take a deeper look at the hierarchical aspect behind it.

Planting a tree

The next thing to understand is that we can use paths to address branches in a tree. Thinking as a software engineer we call the branching points nodes and can give them names. If every node in our imagined tree has a name every branch and every leaf can be denoted with a path by listing all nodenames from the root up to the one we’re interested in. Such a path might be massInC/kyrie/soprano/melody to point to the melody to be sung by the sopranos in the kyrie movement of a piece called massInC. Now it is easy to separate the lyrics massInC/kyrie/soprano/lyrics, the music for the alto-singers massInC/kyrie/alto/melody, the other movements massInC/gloria/soprano/melody or another piece massInD/kyrie/soprano/melody. These paths are comparable to a file path or a URL in a web address: http://www.music-server.example/massInC/kyrie/soprano/lyrics.

If the tree structure follows the same naming conventions on all nodes and their child nodes we can apply the same methods to all leaves — or may I say fruits? In the given example of a hypothetical “massInC”, we want to reach the melody of the soprano voice. From the root of the tree the first node is named “massInC”. From this node emerge five branches with nodes named “kyrie”, “gloria”, “credo”, “sanctus” and “agnus-dei”. And each of these five nodes branch into four nodes named “soprano”, “alto”, “tenore” and “basso”, which carry each two fruits named “melody” and “lyrics”.

The way to massInC/kyrie/tenore/melody

The way to massInC/kyrie/tenore/melody (click to enlarge)

Now that we have this concept in place, implemented through the strict naming scheme, let us recall what we’re after: we want to retrieve the right music variables through a function in order to put them in the right place in the score structure. To achieve this we will store the content in a special kind of variable, an “association list” with the name of musicTree.
(Please note: from now on I will use Scheme code which is partly non-trivial. If you don’t understand it in detail please don’t give up on the post. Its main intent is to demonstrate the underlying concepts and not the concrete implementation. I will not try to explain how the code actually works but what it is meant to do.)

We start with writing two functions \putMusic and \getMusic which store and retrieve the content in the musicTree list:

% the main music tree
musicTree = #'()

% function to store music in `musicTree`
putMusic =
#(define-void-function (parser location path content)(string? ly:music?)
   (let ((pl (string->path path)))
     (set! musicTree (tree-set! musicTree pl content))
     ))

% function to get music from `musicTree`
getMusic =
#(define-music-function (parser location path)(string?)
   (let* ((pl (string->path path))
          (content (tree-get musicTree pl)))
     (if (ly:music? content) content (make-music 'SequentialMusic 'void #t))
     ))

To make these commands work, we first have to define the helper functions tree-set! and tree-get. These do the actual work of dealing with the internal list structures.

% set a value in a tree -- based on nested association lists
#(define-public (tree-set! tree path val)
   (if (> (length path) 1)
       (let ((tab (assoc (car path) tree)))
         (if (not (pair? tab))(set! tab '((car path))))
         (assoc-set! tree (car path) (tree-set! (cdr tab) (cdr path) val))
         )
       (assoc-set! tree (car path) val))
   )
% get a value from a tree -- based on nested association lists
#(define-public (tree-get tree path)
   (let ((ret (assoc (car path) tree)))
     (if (pair? ret)
         (if (> (length path) 1)
             (tree-get (cdr ret) (cdr path))
             (cdr ret))
         #f))
   )
% split a string by '/'
#(define-public (string->path str)(string-split str #\/))

With those methods available we have the tools to composite parts from a tree, for example the melody and the lyrics of a vocal staff. We can now write a function \vocalStaff that creates a staff and retrieves the melody and the lyrics from the “fruits” of the branch given as a string. Another function SATBgroup can then call \vocalStaff four times to create a choir score:

% helper function to append strings
stringAppend =
#(define-scheme-function (parser location str1 str2)(string? string?)
   (string-append str1 str2))

% create one vocal staff
vocalStaff =
#(define-music-function (parser location my-path-to-vocal)(string?)
   ; create a unique name for the voice
   (let ((voice-name my-path-to-vocal))
     #{
       <<
         \new Staff \new Voice = #voice-name { \getMusic \stringAppend #my-path-to-vocal "/melody" }
         \new Lyrics \lyricsto #voice-name { \getMusic \stringAppend #my-path-to-vocal "/lyrics" }
       >>
     #}))

% and now combine four vocal staves to a choir group:
% create vocal staffs for Soprano, Alto, Tenore and Basso
SATBGroup =
#(define-music-function (parser location my-path-to-choir)(string?)
   #{
     \new ChoirStaff <<
       { \vocalStaff \stringAppend #my-path-to-choir "/soprano" }
       { \vocalStaff \stringAppend #my-path-to-choir "/alto" }
       { \vocalStaff \stringAppend #my-path-to-choir "/tenore" }
       { \vocalStaff \stringAppend #my-path-to-choir "/basso" }
     >>
   #})

Now, let us use \putMusic to insert some music into the tree:

\putMusic "massInC/kyrie/soprano/melody" \relative c'' { c2 d4 c }
\putMusic "massInC/kyrie/soprano/lyrics" \lyricmode { Ky -- ri -- e }
\putMusic "massInC/kyrie/alto/melody" \relative c'' { r4 g f e }
\putMusic "massInC/kyrie/alto/lyrics" \lyricmode { Ky -- ri -- e }
\putMusic "massInC/kyrie/tenore/melody" \relative c { \clef "G_8" e4 f( g) g }
\putMusic "massInC/kyrie/tenore/lyrics" \lyricmode { Ky -- ri -- e }
\putMusic "massInC/kyrie/basso/melody" \relative c { \clef "bass" c4 g4 c~ c }
\putMusic "massInC/kyrie/basso/lyrics" \lyricmode { Ky -- ri -- e __ }

and engrave it:

\SATBGroup "massInC/kyrie"

trees-and-music.score_

Finally the different movements of the composition might be collected this way:

\score {
  \SATBGroup "massInC/kyrie"
}
\score {
  \SATBGroup "massInC/gloria"
}
\score {
  \SATBGroup "massInC/credo"
}
\score {
  \SATBGroup "massInC/sanctus"
}
\score {
  \SATBGroup "massInC/agnusdei"
}

You can download the whole example here. (The Gloria, Credo, Sanctus and Agnus-Dei movements are to be composed …)

Let us again line up, what is happening here:

  1. \SATBGroup is called with path "massInC/kyrie"
  2. The function (or template) \SATBGroup calls for all four voices (sopranos, altos, tenors, bassos) — replace tenore with soprano, alto or basso respectively
    \vocalStaff "massInC/kyrie/tenore"
  3. \vocalStaff calls
    <<
      \new Staff { \new Voice = "melody" \getMusic "massInC/kyrie/tenore/melody" }
      \new Lyrics \lyricsto "melody" \getMusic "massInC/kyrie/tenore/lyrics"
    >>
    

The hierarchical naming and storing scheme makes it possible to separate content and layout. A function \SATBTwo might create two staves — with sopranos and altos in the upper and tenors and bassos in the lower stave — accessing the music with the same paths.

vocalTwo =
#(define-music-function (parser location my-path-to-vocal-A my-path-to-vocal-B)(string? string?)
   (let (
          (staff-name (string-append my-path-to-vocal-A my-path-to-vocal-B)) ; a name for the staff-context
          (voice-name-A my-path-to-vocal-A) ; a name for the upper voice-context
          (voice-name-B my-path-to-vocal-B) ; a name for the lower voice-context
          )
     #{
       <<
         \new Staff = #staff-name <<
           \new Voice = #voice-name-A { \voiceOne \getMusic \stringAppend #my-path-to-vocal-A "/melody" }
           \new Voice = #voice-name-B { \voiceTwo \getMusic \stringAppend #my-path-to-vocal-B "/melody" }
         >>
         % align lyrics above the staff and voice A
         \new Lyrics \with { alignAboveContext = #staff-name }
         \lyricsto #voice-name-A { \getMusic \stringAppend #my-path-to-vocal-A "/lyrics" }
         % align lyrics below the staff and voice B
         \new Lyrics \with { alignBelowContext = #staff-name }
         \lyricsto #voice-name-B { \getMusic \stringAppend #my-path-to-vocal-B "/lyrics" }
       >>
     #}))

SATBTwo =
#(define-music-function (parser location my-path-to-choir)(string?)
   (let ( ; create paths for the four voices
          (soprano (string-append my-path-to-choir "/soprano"))
          (alto (string-append my-path-to-choir "/alto"))
          (tenore (string-append my-path-to-choir "/tenore"))
          (basso (string-append my-path-to-choir "/basso"))
          )
   #{
     \new ChoirStaff <<
       { \vocalTwo #soprano #alto }
       { \vocalTwo #tenore #basso }
     >>
   #}))

With those functions available, you can decide whether to engrave the previously stored music in four or in two staves.
And to create a score only with the tenors part, you simply call \vocalStaff "massInC/kyrie/tenore".

Conclusion

Now we have a method to write template functions and to store music in a structured hierarchical manner.
Coming back to the image of a tree, the \SATBGroup builds the trunk of the engravement-tree for a whole movement. It delegates to four \vocalStaff calls, which retrieve music from the named branches of the tree.
And from a software developers point of view the tree represents the musical content — or is kind of a model in the sense of the model-view-controller pattern and the templates are the view on the music-model formed by the tree.

With these ideas in mind I developed a template framework to be used with LilyPond edition projects. The original framework can be inspected or downloaded from lalily@GitHub, but I’m gradually making it available in a slightly more generic form at the openLilyLib repository. Of course, the implementation of the functions differ from the demo functions above. The next posts will develop the examples exposed in this article towards the existing implementations and introduce the usage of them. And this will also lead us to the edition-engraver, which is another important step towards separation of content and layout.

12 thoughts on “Trees, Music and Lilypond

  1. Jacques Menu

    Hello Jan-Peter,

    Thanks a lot for this most useful contribution!

    I’d like to change the breathing sign and the way bar numbers are displayed : what are the best places in the functions to use for such settings?

    Regards,

    PS> I solved the midi production part with:

    \score {
    \SATBGroup “massInC/kyrie”
    \midi {\tempo 4=100}
    }

    Reply
    1. Jan-Peter Voigt Post author

      Hi Jacques,
      thanks for your comment!
      To add such layout settings, you can use the layout block in a score. And you can produce different scores with different layout settings from one music source:

      \score {
        \SATBGroup "massInC/kyrie"
        \layout {
          \context {
            \Score
            \override BarNumber.stencil = #(make-stencil-boxer 0.1 0.4 ly:text-interface::print)
            \override BarNumber.break-visibility = ##(#t #t #t)
          }
          \context {
            \Staff
            \override BreathingSign.font-size = #1
          }
        }
      }
      

      I am working on followup articles, where I am describing the actual implementation in lalily and openlilylib. And I will describe the edition-engraver.

      Reply
  2. Jacques Menu

    Hello Jan-Peter,

    One more question : I’d like to specify the clefs somewhere like in SATBGroup, so as to allow the creation of scores for the instruments in our harmony, such as our saxophones that all use a treble clef.
    How can that be done?

    Thanks!

    Reply
    1. Jan-Peter Voigt Post author

      Hello Jacques,
      there are some more things to explain about the templates. In the actual implementation, all templates have one signature:
      #(define-music-function (parser location music-path options)(list? list?) ...)
      Then one can give arbitrary options to the template in an a-list:

      \callTemplate template.id music-path #'((clef . "G"))

      I hope to finish my follow-up soon, where I will explain it a bit more in detail.

      Reply
  3. Simon Albrecht

    Looks interesting. It’s impressive how easily you seem to be handling “non-trivial” scheme constructs, as you call it – being professionally involved with programming is a large advantage, which I don’t share…
    In the interest of establishing an intuitive naming convention, I’d much prefer the language of the voice names to be consistent: that is to say, either in English: Soprano, Alto, Tenor, Bass (perhaps most consistent with general LilyPond use). Or in Italian: Soprano, Alto, Tenore, Basso. Now it’s something inbetween which I find confusing.

    Reply
  4. Jan-Peter Voigt Post author

    Hello Simon,
    thank you for this hint! Consider it a typo — I meant “tenore”, but then came copy, paste and their cousins 😉
    The idea of this article is to show the advantage and the potential of a hierarchical naming scheme. Personally I prefer short names like “sop”, “alt”, “ten”, “bas” for a SATB score, but I wished to have more “speaking” names here.
    There are templates ready to use, which I will describe in another article, where the user is free to choose any naming he likes. The \SATBGroup function is an example to explain the principle of delegation, but I’d actually recommend to use a flexible template, which can adapt to any naming scheme and to any other choral setup like SAATBB or SSATB or just SAB.

    Reply
  5. Pingback: Towards templating with la-lily-pond | Scores of Beauty

  6. Pingback: Partially Compiling a LilyPond Score | Scores of Beauty

Leave a Reply

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