Programmatically Generating LilyPond Input

One of the advantages of plain text files that I had mentioned in my recent essay is that they can be manipulated and/or generated programmatically. This benefit may seem academic to some of the readers, but another recent post of mine gave me a wonderful opportunity to demonstrate a real-world application for it.

David Aldridge published two exhaustive books on the “Elements of Rhythm”, which seem to have become “classics” already (I’ve talked to several percussionists who considered it natural to know them). He used Finale to create the musical examples in the books, but as I’ve already pointed out it would probably be much more efficient to do this in LilyPond – especially as a big part of the book consists of systematic permutations of all conceivable rhythmical combinations. The two models that are shown on the example page on Finale blog are “0/1 tables” and can be expressed as binary patterns, the first one being

1 0 0 1 0 0 0
1 0 0 1 0 0 1
1 0 0 1 0 1 0
1 0 0 1 0 1 1

or – interpreted as binary numbers – as the sequence 72 73 74 75. In fact this whole section of Aldridge’s book can be reduced to processing a numerical sequence of binary numbers. Dealing with such a task programmatically is an inherently suitable approach because – as Aldridge says himself – doing it manually is a tedious and very error-prone process. Music can be generated directly inside the LilyPond files using Scheme, LilyPond’s extension language. Or – and that’s what I’ll show in this post – by using any programming language to produce the input code LilyPond can then process. Both approaches have their advantages, depending on your knowledge and the given task.

Of course you don’t need to be a programmer to successfully work with LilyPond. But if you are facing tasks like this it is good to know that LilyPond offers this option to get right to the the issues’ source (pun intended). I don’t want to give the impression that it is particularly easy to learn programming but if you know your way around it a little bit (or know someone who does) you have all that power readily available. So I suggest trying to follow my explanations even if you don’t understand every detail while I hope to guide you gently enough through the process.

Who do you think is more powerful?

Who do you think is more powerful?

The challenge I’m going to take is not an exact copy of David Aldridge’s book or even a section of it (for which I just haven’t enough information). Instead I’ll systematically complete the set of exercises begun in the previous post to demonstrate the work-flow in general. The resulting score should span all rhythmical building blocks, starting with 2-element patterns and going right through to 8-element patterns, typeset in the style of last time’s example. If you (re-)read Aldridge’s posts you may get an impression of the workload we’re heading to. But other than him I won’t use a mouse to copy & paste patterns and drag barlines around. Instead I use Python to generate LilyPond input code. 


As said a pattern (in our task description) is a musical representation of a binary number. So I will start my work with writing a function pattern(index) that takes a number and produces LilyPond code for a complete pattern. The most convenient way (for me) is to implement this as a “Python snippet“ in my favorite LilyPond editor Frescobaldi, although it could of course also be done in an external script of any programming language. The immediate result I come up with is:

def pattern(index):
    pat = bin(index)[2:]
    notes = { '1': 'c2 ', '0': 'r2 ' }
    result = ''
    for i in range(len(pat)):
        result += notes[pat[i]]
    return result

The function first converts the numeric argument index (e.g. 72) to a string with its binary representation (in this case "1001000") and then walks through all the characters, converting them to half notes (“c2” for a “1”) and halfnote rests (“r2” for a “0”). The resulting LilyPond code for that function call would be

c2 r2 r2 c2 r2 r2 r2

If you look at the original example you’ll see that the patterns are beamed over the whole bars. Therefore we need to insert manual beams (square brackets) from the first to the last note of the pattern. For this we determine the position of the first and last note events – if there is more than one note event in the pattern:

    # find first and last note (for beaming)
    first = pat.find('1')
    last = pat.rfind('1')
    # check if we have more than one note in the pattern
    if first == last:
        first = last = -1
    beams = { first: '[ ', last: '] ' }

Finally we insert opening and closing brackets or simple spaces at the determined postions.

    # collate pattern from notes/rests and beams
    result = ''
    for i in range(len(pat)):
        result += notes[pat[i]]
        result += beams.get(i, ' ')
    return result, str(len(pat))

This way of dictionary lookup is one of the subtleties (and niceties) of Python syntax but this is not the place to dwell on that. Calling that modified function with 72 as argument now produces the following LilyPond input:

c2[ r2 r2 c2] r2 r2 r2

To create a sequence of all patterns from 2 to 8 elements’ length it now would be enough to write

for i in range(2, 256):
    pattern(i)

but in fact we have to take care of a few more things, particularly inserting a time signature change whenever the length of the pattern changes and start a new exercise after every four measures (or when the time signature changes).

As these considerations aren’t on the structural level of the pattern itsefl but on that of the score I write another (“outer”) function which handles them and repeatedly calls the previous (“inner”) function. This function works on a list of numbers so it is prepared to work on arbitrary sequences of patterns if we should ever want that. First we initialize an empty string that we will finally return and a counter that we’ll use for breaking after four patterns. Then we start our main loop that will walk over the whole list of pattern numbers.

def generate_patterns(numbers):
    content = ''
    cnt = 0
    for i in numbers:

We start the work with querying some information about the current pattern. Of course we need the pattern itself as well as its length (for determining the numerator of the time signature). Then we set two flags: Does the time signature change? i.e. does the length of the pattern change – which happens to be the case when the pattern number is a power of two? The four bars are full when the counter can be divided by four without a remainder.

        # get the pattern and its length
        pat, length = pattern(i)
        # determine if we reach a new pattern length
        # or have completed four bars
        isNewTime = (i & (i - 1)) == 0 # a power of two
        isFourBars = cnt % 4 == 0

Now we optionally insert the time signature change and the start of a new exercise. If we need a time signature change we compute it from the length of the pattern. \newExercise  is a custom command defined elsewhere, which I’ll show you in detail next time.

        # insert an code for a time change or nothing
        time = '\\time ' + length + '/2\n' if isNewTime else ''
 
        # process every four bars _or_ time change
        if isFourBars or isNewTime:
            content += time + '\\newExercise ' + length + '\n'
            cnt = 0
 
        # append the pattern
        content += pat  + ' | % ' + str(i) + '\n'
        cnt += 1

Finally we wrap the whole result into a Scheme function and return the complete sequence of patterns. In the next post I’ll tell you why it isn’t enough to simply create a variable definition from it.

    # wrap into a music function
    start = ('patterns =\n' +
        '#(define-music-function (parser location layer) (number?)\n' +
        '(set! denominator layer)\n' +
        '#{\n' )
 
    end = '#})\n'
 
    #write the result to the document
    return start + content + end

If you aren’t used to programming this may look frightening to you. But please recall what I told you earlier: programming isn’t required at all to be successful with LilyPond. But when special tasks call for it you know it’s there to extend LilyPond’s built-in capabilities (I’m not a programmer or computer scientist myself, but a pianist and musicologist). In this case a tiny, straightforward Python program of only a few dozen lines of code generates the LilyPond input for 254 measures of rhythmic patterns in the fraction of a second.  Actually, I could generate millions of rhythmic patterns if I wanted to, just by running the script with a bigger argument.

If you want to inspect the material somewhat closer you can download


From the example we did last time we already know that we don’t need to bother with making copies of that material for the other metric contexts because LilyPond can generate them on-the-fly. So after roughly one hour of Python coding (a more proficient or even professional programmer would surely come up with that in less than 25 minutes) we can state that we have prepared the contents of our complete set of rhythmic patterns, with all their variations and permutations, without any tedious and error-prone copying. And – this is maybe even more important – if we should later decide to change anything we can simply modify our script and rerun it to get a new document with different content.

One thing I could imagine out of my hat is creating an exercise generator that combines patterns randomly – you could even put that behind a web interface: let the user select some parameters and let LilyPond produce a PNG for display on the web page along with a PDF for printout! And as we are doing this programmatically we have any choice to influence the sequence of patterns. We could for example decide to typeset a sequence of random patterns of continuously decreasing length. Or we could decide if a pattern can be used once or multiple times. Or define a group of patterns that should be trained more intensely than others – in short: we can “create anything we can imagine” …

Now I think this is really nice, but I’m sure one could also achieve the same result directly within the LilyPond source code with a Scheme function which would make a few things even more straightforward. I would be happy to accept any suggestion by the Scheme cracks on lilypond-user. Please post a comment or a pull-request on the Git repository.

In the next and final part of this series I will use our generated content to produce a nice PDF with our collected exercises. Then we can also start thinking about page layout and other settings. Maybe you noticed that I didn’t speak about these so far? That is because text based tools let us concentrate on the content for most of the development process. Making decisions about layout or appearance can wait until it’s actually time to prepare the final printout.

5 thoughts on “Programmatically Generating LilyPond Input

  1. Pingback: “Creating anything you can imagine with Finale” | Scores of Beauty

  2. stefaan

    Nice blog entry. Minor typo: one of the code fragments

    # determine if we reach a new pattern length
    # or have completed four bars
    isNewTime = (i & (i - 1)) == 0 # a power of two

    displays an & instead of &

    Reply
      1. Urs Liska

        Yes, the same tricks the WordPress visual editor is playing on me 😉
        I fixed it, but it seems to be a misbehaviour of the editor, so the error may be reintroduced in case I have to edit the post again …

        Reply

Leave a Reply

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