Skip to content

Music Functions 4: Recursion

Today we'll come to an end with a series of posts demonstrating how to write a comparably simple music function in LilyPond. I deliberately chose to describe the stuff with such verbosity and at that gentle pace because I think that's what is missing in LilyPond's (otherwise excellent) documentation. At least I think I would have greatly benefitted from that kind of explanation ...

In the first part we started considering how to insert a Scheme music function in a LilyPond input file, and in the second part we started writing a function that colors an arbitrary function with an arbitrary color. While this was more or less hard-coded and exposed lots of redundant code we wanted to improve it in the third part through refactoring and list processing. Unfortunately this didn't work out as easily as expected, and finally we'll be solving the task through recursion today.

In a comment to the previous post Jay Anderson pointed out that there is another solution that doesn't require using recursion. However I decided not to change the post that I had already written. So just keep in mind that the following post does not demonstrate the only approach but rather one of several different options. Apart from that I think that while Jay's solution is actually more straightforward as a programming construct it would require considerably more previous knowledge and understanding. Using local variables with Scheme is completely different from other languages' behaviour, so it would warrant a dedicated post or even series of posts.

Recursion

Scheme is a dialect of the LISP family of programming languages. “LISP” is derived from “LISt Processing”, and therefore lists are an ubiquituos concept in Scheme as in all other LISP languages. By now I can see some of the benefits of that approach, but I do insist on saying that it can be quite difficult to get into the spirit of things with Scheme. And until you have achieved this nearly everything about it can be extremely confusing. List handling is one of these things.

To solve our task of calling the coloring function for each grob type you would write something like this in Python:

for grob in my_grob_list:
    color_grob(grob, my_color)

which can be directly translated to English as “For each grob in the list my_grob_list please call the function color_grob, passing it the current grob and the general color my_color as arguments”. In Scheme you have to take a completely different approach by calling a function recursively.

Recursive functions are functions that call themselves with a modified argument list. A very important thing is to insert a check for a state to end the recursion because otherwise the program would get stuck in an infinite loop. The general outline for such a function is:

  • check for the exit state and leave the function if this is true
  • do something with the arguments and recursively call the function itself with modified arguments.

When using recursion to iterate over lists this usually means you will do something with the first element of the list and call the function with the remaining part of the list. The exit check can then determine if there are still list elements present to be processed.

Since our current case of a LilyPond music function complicates matters we'll have a look at a generic Scheme function first:

#(define (display-names my-names-list)
   (if
    ;; check if there still is a list element left
    (null? my-names-list)
      ;; no: we're at the end of the recursion
      ;; simply issue a new line and exit
      (newline)
      ;; yes: we still have elements to process
      ;; begin a compound expression
      (begin
       ;; display the first element and insert a new line
       (display (car my-names-list))
       (newline)
       ;; recursively call the function
       ;; with the rest of the list
       (display-names (cdr my-names-list))) ;; end of begin
      ) ;; end of the if expression
   )

This defines a Scheme function with the name display-names and an argument my-names-list). Other than LilyPond's music functions this doesn't check the type of the argument, but the function body expexts it to be a list. Actually the code comments say it all, and it's the implementation of what has been said above. The only thing to notice is that in LilyPond's Scheme you have to give a “then” and an “else” expression to the if clause. In other Scheme dialects this may be omitted. And you should be very familiar with the car and cdr operators and their relatives. car returns the first element of a pair or list. cdr returns the second element of a pair, or with a list it returns a new list with all the elements of the list except for the first one (the "rest" of the list). (Or with Scheme lists you can consider a list to be a pair with one first element (the car) and the second element (the cdr) being a list on its own.)

Now you can call that function with

#(display-names '(NoteHead Slur Flag Beam))

and see your list of grob types printed nicely in the console output.

Applying It To Our Task

You will recall that we wanted to write a function \colorGrobs that takes a list of grob names and a color as its arguments. In the last post we already wrote the function signature and now we can apply our recursion experience to write the body of that function. Finally it looks quite concise too:

colorGrobs =
#(define-music-function (parser location my-grob-list my-color)
   (symbol-list? color?)
   (if (null? my-grob-list)
       ;; issue an empty music expression
       #{ #}
       #{
         % color the first grob type of the current list
         \colorGrob #(car my-grob-list) #my-color
         % recursively call itself with the remainder
         % of the current list.
         \colorGrobs #(cdr my-grob-list) #my-color
       #}))

We define a music function that takes a list of grob names and a color as its arguments. When the list is empty the recursion stops by issuing an empty music expression. This is very handy as this way you can write an empty part for the if clause, something you can't do in pure Scheme code in LilyPond. If there is an element left we call \colorGrob passing it the first list element as the grob name argument and then recursively call \colorGrobsitself with the grob name list stripped of its first element. That's what we do with the car and cdr operators.

Now we have to write a corresponding \uncolorGrobs function and put everything togehter that we have done so far. I won't spend all the screen estate to quote it all once more, but you can download the complete LilyPond file to run and inspect.

More Cleaning Up

OK, we have achieved our goal, a function that colors an arbitrary music expression in an arbitrary color. But I won't let you go home now because there are still some things that can be improved with regard to “code hygiene”. The first issue will be some more factoring out to get rid of redundant code, the second will make the function still more generic.

Factoring Out Even More

One thing I don't like about our solution so far is that we have to write several things twice, for coloring and uncoloring. It would make sense to merge the respective functions into one by providing yet another argument telling which direction we are going to do.

First we update our \colorGrob function to take an additional boolean color-on argument. If that is set to #t (true) we apply the \override, otherwise we \revert it:

colorGrob =
#(define-music-function (parser location my-grob my-color color-on)
   (symbol? color? boolean?)
   (if color-on
       #{
         \temporary \override #my-grob #'color = #my-color
       #}
       #{
         \revert #my-grob #'color
       #}))

The necessary change to our outer function \colorGrobs is even smaller. We don't need to add a new conditional clause here but can simply extend the argument list by the boolean argument color-on and pass this on to \colorGrob unchanged.

colorGrobs =
#(define-music-function (parser location my-grob-list my-color color-on)
   (symbol-list? color? boolean?)
   (if (null? my-grob-list)
       ;; issue an empty music expression
       #{ #}
       #{
         % color the first grob type of the current list
         \colorGrob #(car my-grob-list) #my-color #color-on
         % recursively call itself with the remainder
         % of the current list.
         \colorGrobs #(cdr my-grob-list) #my-color #color-on
       #}))

Now we have to update our entry function \colorMusic to call the new internal function accordingly. Here we get a little inconsistency: Instead of calling \uncolorGrobs we now call \colorGrobs and have to pass a color as an argument although uncoloring doesn't need a color information at all. I will accept this for today because it doesn't add too much redundancy and because it is in code that the user won't ever have to enter. The clean solution would be to work with optional arguments but we'll defer this topic to a future post. As before I won't copy all the stuff but provide the complete file for download.

Use a Generic Grob List

One last thing we will improve in our function is the list of grob names that has to be passed into the function. This requires some typing and especially it makes it somewhat random whether all relevant grobs have been taken care of. Fortunately LilyPond provides a means that can give us all we need to make this really generic: the all-grob-descriptions function. This produces a nested list of pairs, where each pair consists of a grob name and a list of its properties. Now we can make use of several things we learnt during this tutorial series: map, lambda, car and cdr.

What we need is a list of all grob names, that is the first element of all pairs that make up the all-grob-descriptions list. In other words we need to map the cars of all elements of this list to a new list. You should remember that map takes the elements of a list and passes it to a function and creates a new list of the results of that function, and we can use lambda to create this inner function ad-hoc.

(map (lambda (gd) (car gd)) all-grob-descriptions)

is all we need to perform this. The inner function will produce the car of all elements it is passed, map provides the elements of the all-grob-descriptions list and will produce the new list containing all grob names that LilyPond knows. You may check the result of this by displaying it. Just put

#(display (map (lambda (gd) (car gd)) all-grob-descriptions))

in an otherwise empty LilyPond file and compile it. This won't produce a PDF but instead lists all available grob names in the console output.

The last step to do is to enclose this line in a newly written Scheme function and call this function when we need the list of grob names:

allGrobNames =
#(define-scheme-function (parser location)()
   (map (lambda (gd) (car gd)) all-grob-descriptions))

colorMusic =
#(define-music-function (parser location my-color music)
   (color? ly:music?)
   #{
     \colorGrobs \allGrobNames #my-color ##t

     #music

     \colorGrobs \allGrobNames #my-color ##f
   #})

One thing to note with this is that using \allGrobNames significantly slows down the compilation of our test music. I didn't check the impact on large scores, but in the end you may have to evaluate the tradeoff between a generically comprehensive solution and processing speed in the context of your own concrete project.


As a final listing I will now print the whole file - which has become comparably short by now - and the resulting score - now the accidentals and dynamics are colored as well.

\version "2.18.0"

colorGrob =
#(define-music-function (parser location my-grob my-color color-on)
   (symbol? color? boolean?)
   ;; check for the boolean argument
   (if color-on
       ;; either set the color for the grob type
       #{
         \temporary \override #my-grob #'color = #my-color
       #}
       ;; or revert it
       #{
         \revert #my-grob #'color
       #}))

colorGrobs =
#(define-music-function (parser location my-grob-list my-color color-on)
   (symbol-list? color? boolean?)
   (if (null? my-grob-list)
       ;; issue an empty music expression
       #{ #}
       #{
         % color the first grob type of the current list
         \colorGrob #(car my-grob-list) #my-color #color-on
         % recursively call itself with the remainder
         % of the current list.
         \colorGrobs #(cdr my-grob-list) #my-color #color-on
       #}))

allGrobNames =
#(define-scheme-function (parser location)()
   ;; create a list with all grob names from LilyPond
   (map (lambda (gd) (car gd)) all-grob-descriptions))

colorMusic =
#(define-music-function (parser location my-color music)
   (color? ly:music?)
   #{
     \colorGrobs \allGrobNames #my-color ##t

     #music

     \colorGrobs \allGrobNames #my-color ##f
   #})

music = \relative c' {
  c4. d8 e16 d r cis( d4) ~ | d1 \fermata
}

\relative c' {
  \colorMusic #blue \music
  \colorMusic #red { c4 c } d \colorMusic #green e\f
  \colorMusic #magenta \music
}

[caption id="attachment_2544" align="aligncenter" width="625"](Click to enlarge) (Click to enlarge)[/caption]

As you can see our project has turned into a number of short functions (they could have been formatted even more concisely, and some of the comments could be left out in real life). It is characteristic when using Scheme to have many functions that are defined somewhat pyramidal: from the most broad functions at the bottom up to ever more specific and small functions at the top of the file. This is due to the nature of Scheme consisting of expressions that are evaluated rather than of commands that are executed. With Scheme you don't generally write a chain of commands or loops within one function but rather replace any compound statement with a function call.

Of course - and explicitly pointing this out may be important to a significant share of the readers - I would now put all code above the music variable definition in a library file and \include this. So using the \colorMusic function doesn't have any bigger impact on the structure and readability of the actual music input files.


We have come to the conclusion of our little series of tutorial posts. I hope you enjoyed them and have learned something along the way. Maybe writing Scheme functions isn't all that daunting to you anymore - that would be a great outcome of my efforts. At least for me this was the case. I think I've taken the next step in tackling Scheme - of course I'm now waiting for the next occasion where I feel stupid not managing to cope with seemingly simple tasks ...

Feel free to improve the tutorials by asking questions or adding examples in the comments. And if you have something to say on the matter or on other Scheme related topics don't hesitate to contribute your own tutorial.

{% credits %}{% endcredits %}


Last update: November 3, 2022