Music functions 3: Reusing Code

In the last post We wrote a music function that took an arbitrary music expression and applied an arbitrary color to it. The problem we immediately saw is that we manually had to override all properties – and that we still didn’t manage to catch them all. In general terms, a function that redundantly calls \override for each new property isn’t exactly elegantly programmed. So today we will do better and at the same time learn something about reusing code by refactoring and by processing lists.

This was the function as we wrote it originally:

\version "2.18.0"

colorMusic =
#(define-music-function (parser location my-color music)
   (color? ly:music?)
   #{
     \temporary \override NoteHead.color = #my-color
     \temporary \override Stem.color = #my-color
     \temporary \override Flag.color = #my-color
     \temporary \override Beam.color = #my-color
     \temporary \override Rest.color = #my-color
     \temporary \override Slur.color = #my-color
     \temporary \override PhrasingSlur.color = #my-color
     \temporary \override Tie.color = #my-color
     \temporary \override Script.color = #my-color
     \temporary \override Dots.color = #my-color
     
     #music
     
     \revert NoteHead.color
     \revert Stem.color
     \revert Flag.color
     \revert Beam.color
     \revert Rest.color
     \revert Slur.color
     \revert PhrasingSlur.color
     \revert Tie.color
     \revert Script.color
     \revert Dots.color
   #})

Start factoring out common functionality

For each notation element in question we called \temporary override NNNN.color = #my-color, so the first thing we’ll do is factoring out this common functionality in a dedicated function. This will be a helper function usually not called directly. Each use of \override could be enclosed in a music expression so we’ll write a new music function that returns such a music expression.

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

As you can see this function takes two arguments: a grob name (GRaphicalOBject) and a color. The color has the type we already know for it (color?) while the grobname has to be given as a symbol?. In the function body the \temporary \override is applied to the given grob with the given color. So now we can call the function with \colorGrob NoteHead #red, and as we already have the color as the argument to our entry function \colorMusic we can use \colorGrob NoteHead #my-color in our main function.

For uncoloring the music we write a corresponding function \uncolorMusic that uses \revert instead of \override. Our modified main function and its two new helper functions now look like this:

\version "2.18.0"

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

uncolorGrob =
#(define-music-function (parser location my-grob)
   (symbol?)
   #{
     \revert #my-grob #'color
   #})

colorMusic =
#(define-music-function (parser location my-color music)
   (color? ly:music?)
   #{
     \colorGrob NoteHead #my-color
     \colorGrob Stem #my-color
     \colorGrob Flag #my-color
     \colorGrob Beam #my-color
     \colorGrob Rest #my-color
     \colorGrob Slur #my-color
     \colorGrob PhrasingSlur #my-color
     \colorGrob Tie #my-color
     \colorGrob Script #my-color
     \colorGrob Dots #my-color

     #music

     \uncolorGrob NoteHead
     \uncolorGrob Stem
     \uncolorGrob Flag
     \uncolorGrob Beam
     \uncolorGrob Rest
     \uncolorGrob Slur
     \uncolorGrob PhrasingSlur
     \uncolorGrob Tie
     \uncolorGrob Script
     \uncolorGrob Dots
   #})

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

}

which gives the same result as in the last post:

(Click to enlarge)

(Click to enlarge)

OK, this doesn’t make our entry function that much shorter, but we have already achieved a significant first step: avoiding the repetition of the same code. This makes it possible to maintain this code in one place. For example, I told you that \temporary is a rather new command and that you may just skip that command if you want to use LilyPond 2.16 for some reason. Now that we have factored out the command into a function you can simply update this function once and have the modified code for all instances of the function. Or imagine that you want to temporarily disable the function completely, then you can simply comment out the line in the function, and everything will remain black. Nevertheless this has only been the first step …

Using a List as Argument

We haven’t yet arrived at the goal because we still need to call our helper functions for each grob type individually. The goal would be to use only a single function call, and that can be achieved by passing the grob names as a list. So we want to write a function with the following signature:

colorGrobs =
#(define-music-function (parser location my-grob-list my-color)
   (symbol-list? color?)
   #{
     
   #})

This function takes a list of grob names and the color and will then iterate over this list to apply the overrides (using the helper functions we have already written). We’d call that function like this from our main function:

\colorGrobs #'(NoteHead
               Stem
               Flag
               Beam
               Rest
               Slur
               PhrasingSlur
               Tie
               Script
               Dots
               DynamicText
               Accidental) #my-color

This will make our main function much more concise and maintainable because adding a newly noticed grob simply requires adding its name to the my-grob-list list.

Iterating Over the List

While writing the signature of the \colorGrobs function was really easy iterating over that list of grob names is something that really can drive you crazy if you’re not yet really familiar with Scheme. And I have to admit it drove me crazy while writing this post – which you can take as an indication that I won’t be able to continue this series too much further 😉

A straightforward way to iterate over a list that is comprehensible for people coming from other programming languages is the map operation. It takes a function and a list of values and applies the function to each of the values one by one. It returns a new list of modified values.

(map colorGrob my-grob-list)

will walk over our list of grob names and pass each one to the colorGrob function. While this looks quite concise it doesn’t take the color argument into account, so we have to look further.

Seemingly the solution to this issue would be the lambda construct which creates an ad-hoc procedure (basically an unnamed function) that can use any number of arguments.

((lambda (arg) (colorGrob arg my-color)) NoteHead)

will create a function with the body (colorGrob arg my-color). arg is defined to be the argument passed into the function, and NoteHead is passed into it, so colorGrob will actually be called (colorGrob NoteHead my-color). This example is quite useless of course and only there to show how lambda can work with hard-coded and variable arguments, but can be made fruitful together with map.

(map (lambda (arg) (colorGrob arg my-color)) my-grob-list)

will walk over my-grob-list and pass the items one by one to the ad-hoc (lambda) function. This way we iterate over the list of grob names passing them each to colorGrob, accompanied by the static color argument (my-color).

Unfortunately, our journey isn’t at its end. As said map applies the elements of a list to a given function. This function evaluates to a single value (which is what each Scheme expression or function does), and map creates a new list of all these values. This is very useful for manipulating lists in general, but in our case this is a problem: we have to return a music expression, but map creates a list of music expressions. So unfortunately we can’t go that way and have to apply real recursion. (The Guile manual documents map and lambda, and is a helpful resource for Scheme in general.)

Preliminary Conclusion

Today we seemingly didn’t achieve too much: we factored out some code and then defined the goal where we want to go next. But on the way to that goal we arrived at a dead end. But of course I described this on purpose because I hope discussing these things at such a slow pace will give you an opportunity to get familiar with some of the peculiarities of how Scheme works. While map and lambda don’t help us with the current issue they are indispensable tools for your career as a LilyPond-Scheme programmer, and it’s good to get to grips with them as early as possible.

We will complete this exercise in the next post and learn about a very fundamental concept of Scheme: recursion.

14 thoughts on “Music functions 3: Reusing Code

  1. ming

    Very informative for learning Scheme code for lilypond. I have part 1, 2 and 3 together. I learn much and I am looking forward for next part(s).
    Thanks you, Urs.

    Reply
    1. Urs Liska

      Thank you.
      Well, there will be a concluding fourth article (already written), and that will be it for some time. First I don’t know too much more (although the final result of the posts might suggest something different), second, I have to do something else again.

      But I’d be more than happy if others would share some knowledge here.

      Reply
  2. Caio Barros

    Urs, can you feel the high rates of love I’m sending each time I read a post form this series? Learning a lot here, keep them coming!

    How can I know what types of arguments are available? For example, I’m trying to write a function that will show the tuplet number as num:den only once, so I can call it for unusual tuplets, but keep the regular tuplet look when this is not necessary. The problem is that I don’t know what type of argument to add after the (parser location my-argument):

    tupletFraction = #(define-music-function (parser location tuplet-fraction)
    ; what type of argument goes here?
    (unkonw-argument-type)
    #{
    \once \override TupletNumber.text = #tuplet-number::calc-fraction-text
    \tuplet #tuplet-fraction
    #}
    )
    
    \relative c' {
    % uncommon tuplet here
    c4 c \tupletFraction #5/3 { c16 c c c c } c c4 |
    % common tuplet can be shown with the traditional look
    c4 c \tuplet 3/2 { c c c } |
    }

    Is there a list of common used types of arguments used in LilyPond or something like that?

    Reply
    1. Urs Liska

      Sorry, I have to pass this on and hope that someone will answer your question. I’d be interested too. Looking up this kind of thing is often frustrating as long as you don’t know it by heart.

      I’m quite sure such a tuplet fraction should be expressed as a Scheme pair (predicate pair?) and passed as #'(5 . 3), but I don’t know how you should proceed from there.

      Reply
      1. Urs Liska

        Hey, I found it 🙂

        My idea was right, a pair does work here. But you also have to pass it the music

        tupletFraction =
        #(define-music-function (parser location tuplet-fraction music)
           (pair? ly:music?)
           #{
             \once \override TupletNumber.text = #tuplet-number::calc-fraction-text
             \tuplet #tuplet-fraction #music
           #}
           )
        
        \relative c' {
          % uncommon tuplet here
          c4 c \tupletFraction #'(5 . 3) { c16 c c c c } c c4 |
          % common tuplet can be shown with the traditional look
          c4 c \tuplet 3/2 { c c c } |
        }
        Reply
        1. Caio Barros

          Hey, thanks!

          Now, I didn’t understand what “pass it the music” means. I understand that you had to add a “music” argument which has the “ly:music? ” type, but, what is its function? Why do I have to add this?

          Reply
          1. Urs Liska

            If I have understood everything correctly the music function returns one music expression. In our case \tupletFraction #'(5 . 3) { c16 c c c c } is the music expression. You can’t let the function output \tuplet 5/3 and then write the actual music.
            Therefore you pass the music to the function where it will be turned into a music expression together with the \tuplet definition

            Reply
        2. Janek Warchoł

          Hi,

          You will probably be interested to know that you don’t have to write #'(5 . 3) – you can simply write \tupletFraction 5/3 { c16 c c c c } and it will work (LilyPond automatically converts fractions into pairs). 🙂

          Reply
  3. Jay Anderson

    I hope I’m not stealing too much of your thunder, but you can still use your map function without resorting to explicit recursion:

    % Wraps the input list of music elements in a single sequential music element.
    #(define (wrap-music-list music-list)
      (let ((out #{ {} #} ))
        (ly:music-set-property! out 'elements music-list)
        out))
    
    colorGrobs =
    #(define-music-function (parser location grobs color)
      (symbol-list? color?)
      (wrap-music-list (map (lambda (arg) #{ \colorGrob $arg $color #}) grobs)))
    
    uncolorGrobs =
    #(define-music-function (parser location grobs)
      (symbol-list?)
      (wrap-music-list (map (lambda (arg) #{ \uncolorGrob $arg #}) grobs)))

    The wrap-music-list function has some things which would need more introduction to understand:
    – Local variables (let (…) …)
    – Lilypond’s internals and scheme API

    In general I think recursion is something to keep as low-level and self-contained as possible. It is definitely something someone working in scheme needs to understand though.

    This is a great introduction to scheme + lilypond. Keep it up.

    Reply
    1. Urs Liska

      Thanks for that suggestion. Wouldn’t you be able to write that introduction to let and friends? Of course you could build upon/refer to my existing posts.
      Maybe as an alternative approach after my fourth article has been published?

      Reply
  4. ming

    Jay Anderson:
    I reply to music function: part 4 and ask for sample. Now I go back to part 3 and found the sample. Thanks

    Reply
  5. Pingback: Les fonctions musicales 3. Du code réutilisable | Scores of Beauty

Leave a Reply

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