Wednesday, April 13, 2022

Songbooks and Makefiles

Back in 2019, I mentioned polishing up my old mup skills to do some music arrangement. Since then, I have idly thought about putting together some kind of family songbook. Mup is great for lead sheets, but after the family was enjoying listening to some Aquabats songs while painting miniatures, I decided I also wanted to easily make lyric and chord sheets. After some searching, I ended up using ChordPro. These were two separate projects, but I always had in the back of my head that I wanted to bring them together into one sort of family songbook.

My third son has really taken to playing trumpet. Every couple of days, especially since my dad visited, he will grab his horn, and I will grab my guitar, and we'll jam on some easy patterns. Occasionally we get other family members to join in on a spare horn, the keyboard, or some kind of percussion. It's been a lot of fun. It also points to one of the advantages of having typed up my songs: it's easy to produce arrangements in other keys. My mup files, for example, contain simple macros to check whether I have called the application with the -DTRUMPET flag, and if so, transpose the song up a step.

ifdef TRUMPET
  transpose=up maj 2
endif

Last Saturday, I devoted myself to actually pulling together these various mup and chordpro arrangements into one easy-to-reproduce songbook. The tool I ended up using is the classic standard in GNU/Linux: GNU Make. I went for years not having need of Make, but I recently went back to it to handle building Gather 'Round, my family's project for Global Game Jam 2021. Global Game Jam requires a very particular format for archive files, containing source code, builds, promotional materials, and licenses. I had previously managed this manually or with bash scripts, but in 2021, I wrote this Makefile to automate the process. I used a nearly identical one in 2022 for Juxtaposition.

If you look at those makefiles, they are pretty tame. They use the standard features of Make that I learned back in the 1990s: define a target, list its dependencies, provide the rules to build the target. As I started working on my family songbook, I did something similar, listing each .mup file as its own target, with its own source, and with its own command line defining the build process. This was very clearly a DRY problem. I knew how to do wildcards in bash scripts, although every time I write such a bash script, I have to re-learn its idiosyncratic syntax. I figured Makefiles must have something like this, but I had no idea what it was, and I had never seen it.

A bit of stumbling through the information superhighway helped me learn about GNU Make's pattern rules. Combining what I learned from a few StackOverflow posts with the official docs helped me come up with a much simpler and more elegant solution. Whereas I was originally going to use separate directories for mup and chordpro files, with a toplevel Makefile to combine them, this proved awkward without providing real value. I brought all the files into one location and wrote the Makefile shown in the following gist.

The wildcard command gives me what I knew how to do in bash, and the patsubst command is an elegant solution to transforming the strings that are matched by the wildcard. We can look at the simplest case here, the chordpro processing, to understand how these pieces fit together. I'll pull in the relevant variable definitions and targets to aid in explanation.

CHORDPRO_IN  = $(wildcard *.pro)
CHORDPRO_OUT = $(patsubst %.pro,%.pdf,$(CHORDPRO_IN))

$(CHORDPRO_OUT): %.pdf : %.pro
    $(CHORDPRO) $<

CHORDPRO_IN will match all the chordpro input files, any file that ends in .pro. CHORDPRO_OUT then is defined as the same set of files, but with the .pro replaced by .pdf. For each of these expected output files, a target is defined, with the target being the .pdf file, and its dependency being the .pro file. The actual build command is a simple invocation of chordpro, its only command-line argument being the first dependency, which, again, is the .pro input file. Pretty nice.

The Makefile contains a few other tricks. The cover page is a simple affair created in LaTeX. The separate PDFs are combined using pdfunite, which I had never heard of before but certainly did the trick. I ran into one problem with combining the files: if I had mup output PDF directly, the combined file contained errors. I worked around this by having mup output postscript instead, then using the classic ghostscript ps2pdf to do the conversion.

No comments:

Post a Comment