LilyPond Music Notation inside Max/MSP

I often find it easier to understand what I'm hearing when I can see a visual representation of the music. This has become more important as I've been experimenting with generative music. I'm starting to give up full control over the composition process and it's not always clear what the results are without a lot of repeated listens.

Unfortunately, Max/MSP doesn't have much support for standard music notation. So I've been experimenting with generating notation inside Max/MSP using LilyPond, my ajm.ruby object, and the jweb object from Max 5. This isn't a great solution: it's slow and it can only display notation. You can't interact with the notation in any way, which is what I'd really want. But if you don't want to pay money or spend a ton of time writing your own notation software, this is potentially a viable solution for getting notation inside Max.

If you want to try this out, first you need to install:

Then:
  1. download this zip file which contains everything else you need to run this example.
  2. Unzip maxlily.zip somewhere on your Max file path
  3. Edit maxlily.rb and change the LILYPOND and VERSION values to find the lilypond command on your system
  4. Open maxlily.maxpat and click the evaluate button.
It can be slow! If you don't see errors in the Max window then some notation should eventually appear:

I'm not going to give a tutorial on LilyPond, so check out the documentation on their site if you want to play around with notating different things.

There's a bonus on OS X (maybe Leopard only?): jweb uses webkit, which has some nice controls for viewing pdf. If you click on the notation, controls will appear that allow you to zoom in or out, and open the pdf in Preview (where you can print).

So what's going on under the hood? The maxlily.rb file is Ruby code that does most of the work. Ruby is certainly not the only solution here, I just find it very easy to work with. This code could be ported to C or Java or whatever without much difficulty. If you are interested in doing that, let's take a look at the code in detail and see what it's doing:

First some constants are setup:

LILYPOND = '/Applications/LilyPond.app/Contents/Resources/bin/lilypond'
VERSION = '2.10.33'
FORMAT = 'pdf'  
As mentioned above, you will need to modify these to match the version and path to lilypond on your system. Format determines the output format. There are some other choices like png, but pdf seems to look the best.

These lines setup file paths:

THIS_DIR = Pathname.new($0).parent
INPUT = THIS_DIR + 'tmp.ly'
OUTPUT = THIS_DIR + 'output'
THIS_DIR is set to the folder containing the Ruby script. From there we set the location of two files we will need. INPUT is the .ly file that will be generated and sent to the lilypond command in order to produce our notation, and OUTPUT will be the final output file that is loaded into jweb.

Skipping down to the bottom of the program, note that write_to_file() and load_page() methods do what the names imply. write_to_file() writes some plaintext to a file. load_page() sends a url to the jweb object. For proper functioning inside Max, ajm.ruby needs the attribute @evaloutlet -1 so it won't output the script return value and confuse jweb.

That leaves the notate() method, which does all the real work. First we build a multiline string that will be written out to the .ly file for the lilypond command to interpret. Extra backslashes are needed for the usual character escaping. What we are trying to write to disk is:

\include "english.ly"
#{str}
\version "#{VERSION}"
Where #{str} is whatever is passed to the ajm.ruby object and VERSION is the constant defined at the top of the script. I prefer to use lilypond in english language mode, hence the first line (here are other options). The version line is recommended to be included in every .ly file so lilypond knows how to correctly interpret the file as new versions introduce new features.

That text is written to disk as an .ly file, and we tell jweb to display a loading page for user friendliness:

write_to_file(INPUT, source)
load_page(THIS_DIR + 'loading.html')
Next the actual lilypond command is constructed:
%Q["#{LILYPOND}" -f #{FORMAT} -o "#{OUTPUT}" "#{INPUT}"]
I'm using %Q[] to quote the string because I don't want to escape all the double quotes. Double quotes wrap all the paths in order to handle any spaces in the path.

Finally the command is executed, and we do some error checking on the results. If everything looks good we tell jweb to display the resulting file.

Now here's perhaps the most important detail. When ajm.ruby executes, it will cause Max to wait while it's doing it's processing. Running lilypond is a long-running command (5-10 seconds on my computer). If we didn't do anything special, Max would hang for that time and the UI would be unresponsive. That's very bad. So we have to do all the execution of the lilypond command in a background thread. Luckily that's really easy in Ruby: just wrap the code in

Thread.new { }

So that's that. This isn't an ideal solution to music notation in Max/MSP but it works. Next steps would be to write an automated converter between whatever representation is being used for music in the Max patch and the lilypond format. A lot of patches probably don't have an explicit music representation, or every patch might be different, so writing the conversion to lilypond may be more trouble than it's worth. Still, this patch can be a fun way to learn LilyPond syntax and play around with it in an interactive environment.



Adam Murray, 2008
contact the author...