E. Lamprecht’s MZ2SYNTH is a delightfully weird piece of code. It is an advanced wavetable synthesizer programmed only by an input image. Here’s an example:
Documentation is pretty sparse, so I’ve had to work it out as best I can:
input data must be a 720 px high NetPBM PPM or PGM image with a black background
waveforms are specified by pixel colour: sine, square, sawtooth and triangle are red, green, blue and luminance
dynamics are manipulated by changing the pixel brightness
the input plays at a constant rate along the horizontal pixels, defaulting to 10 pixels/second
The pitch is specified by the Y coordinate. To convert from MIDI note number n to an input coordinate for mz2synth, use this formula: y=6×(140 – n) So for Middle C (MIDI note 60), the Y coordinate would be 480.
I’ve created a very simple example that plays a C major scale with simple sine waves with no dynamics.
The input image:
The resulting audio:
And the python code that produced the image:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# mz2-draw - draw a Cmaj scale in the right input format for mz2synth
# scruss, 2025-11
# mz2synth - https://github.com/frankenbeans/MZ2SYNTH
# command line:
# mz2 -v -o mz2-cmaj.au mz2-cmaj.ppm
from PIL import Image, ImageDraw
# convert midi note number (20..127) to
# vertical offset for mz2 input
# notes < 20 (G#0) can't be played by mz2
def midi_to_y(n):
return 6 * (140 - n)
middle_c = 60
maj_scale = (0, 2, 4, 5, 7, 9, 11, 12)
# maj_chord = (0, 4, 7)
# mz2 input must be 720 px high,
# preferably black bg
im = Image.new("RGB", (10 * len(maj_scale), 720), "black")
draw = ImageDraw.Draw(im)
for i, d in enumerate(maj_scale):
# bright red lines mean full
# volume sine waves
draw.line(
[
10 * i,
midi_to_y(middle_c + d),
10 * i + 8,
midi_to_y(middle_c + d),
],
"red",
1,
)
# mz2 can only read NetPBM PPM format
im.save("mz2-cmaj.ppm")
Building
mz2synth comes with Windows and Mac OS binaries. To run the Mac code, you need Homebrew with the gcc@13 recipe. See this issue for details.
To build on Linux, you’ll need gfortran. A build script could be something like this:
git clone https://github.com/frankenbeans/MZ2SYNTH.git cd MZ2SYNTH/SOURCE make -f Makefile.gfortran
Put the resulting mz2 binary somewhere in your path, and that’s all the installation it needs. These same instructions should work for Mac OS.
If you really want to live on the edge (note: not really) and get a faster binary at the expense of array bounds checking, use this to recompile instead of the above make line:
“Subroutines do, however, bring with them considerable overheads in both space and execution time.â€
Imagine you have a programming task that involves parsing and analyzing text. Nothing complicated: maybe just breaking it into tokens. Now imagine the only programming language you had available:
has no text handling functions atall: you can pack characters into numeric types, but how they are packed and how many you get per type are system dependent;
allows integers in variables starting with the letters I→N, with A→H and O→Z floating point;
has IF … THEN but no ELSE, with the preferred form being IF (expr) neg, zero, pos where expr is the expression to evaluate, and neg, zero and pos are statement labels to jump to if the evaluation is negative, zero or positive, respectively;
has only enough memory for (linear, non-associative) arrays of a couple of thousand entries;
disallows recursion completely;
charges for computing time such that a solo researcher’s work might cost many times their salary in a few weeks.
The programming language used is USA Standard FORTRAN X3.9 1966, commonly known as Fortran IV after IBM’s naming convention. For all it looks crude today, Fortran was an efficient, sod-the-theory-just-get-the-job-done language that allowed numerical problems to be described as a text program and solved with previously impossible speed. Every computer shipped with some form of Fortran compiler at the time. Day wasn’t alone working within Fortran IV’s text limitations in the early 1970s: the first Unix tools at Bell Labs were written in Fortran IV — that was before they built themselves their own toolchain and invented the segmentation fault.
The book is a small (~ 90 page) delight, and is a window into system limitations we might almost find unimaginable. Wanna create a lookup table of a thousand entries? Today it’s a fraction of a thought and microseconds of program time. But nearly fifty years ago, Colin Day described methods of manually creating two small index and target arrays and rolling your own hash functions to store and retrieve stuff. Text? Hollerith constants, mate; that’s yer lot — 6HOH HAI might fit in one computer word if you were running on big iron. Sorting and searching (especially without recursion) are revealed to be the immensely complex subjects they are, all hidden behind today’s one-liner methods. Day shows methods to simulate recursion with arrays standing in for pointer stacks of GO TO targets (:coding_horror_face:). And if it’s graphics you want, that’s what the line printer’s for:
Why do I like this book enough to track down a used copy, import it, scan it, correct it and upload it to the Internet Archive? To me, it shows the layers we now take for granted, and the privilege we have with these hard problems of half a century ago being trivially soluble on a $10 computer the size of a stick of gum. When we run today’s massive AI models with little interest in the underlying assumptions but a sharp focus on getting the results we want, we do a disservice to the years of R&D that got us here.
The ‘charges for computing time’ comment above is from Colin’s website. Early central computing facilities had the SaaS billing down solid, partly because many mainframes were rented from the vendor and system usage was accounted for in minute detail. Apparently the system Colin used (when a new lecturer) was at another college, and it was the custom to send periodic invoices for CPU time and storage used back to the user’s department. Nowhere on these invoices did it say that these accounts were for information only and were not payable. Not the best way to greet your users.
(Incidentally, if you hate yourself and everyone else around you, you can get a feel of system billing on any Linux system by enabling user quotas. You’ll very likely stop doing this almost immediately as the restrictions and reporting burden seem utterly alien to us today.)
While the book is still very much in copyright, the copy I have sat unread at Lakehead University Library since June 1995; the due date slip’s still pasted in the back. It’s been out of print at Cambridge University Press since May 1987, even if they do have a plaintive/passive aggressive “hey we could totally make an ebook of this if you really want it†link on their site. I — and the lovely folks hosting it at the Internet Archive — have saved them from what’s evidently too much trouble. I won’t even raise an eyebrow if they pull a Nintendo and start selling this scan.
Colossal thanks to Internet Archive for making the book uploading process much easier than I thought it was. They’ve completely revamped the processing behind it, and the fully open-source engine gives great results. As ever, if you assumed you knew how to do it, think again and read the How to upload scanned images to make a book guide. Uploading a zip file of images is much easier than mucking about with weird command-line TIFF and PDF tools. The resulting PDF is about half the size of the optimized scans I uploaded, and it’s nicely tagged with metadata and contains (mostly) searchable text. It took more than an hour to process on the archive’s spectacularly powerful servers, though, so I hate to think what Colin Day’s bill would have been in 1972 for that many CPU cycles … or if even a computer of that time, given enough storage, could complete the project by now.
The 029 (as it is sometimes known) generated a bitmap font from an engraved metal plate pressing on a matrix of pins. A picture of this plate from a field engineering manual was used to re-create the pin matrices, and thus an outline font.
029 Code Plate
029 Code Key
Historical Accuracy
The 029 could have many different code plates, but the one used here contained the characters:
The character glyphs have been sized such that if printed at 12 points, the 029’s character pitch of 0.087″ is accurately reproduced. No attempt to research the pin matrix pitch or pin diameter has been made: the spacing was eyeballed from a couple of punched cards in my collection.
The earlier IBM Type 26 Card Punch (“026”) included a glyph for a square lozenge (Unicode U+2311, ⌑). The 029 code plate did not include this character, but I added it here for completeness.
The character set was extended to include:
all of ASCII, with lower case characters repeating the upper case glyphs;
sterling currency symbol; and
euro currency symbol.
While there may have been official IBM renditions of some of these additional glyphs (with the exception of euro) no attempt has been made to research the original shapes. This font set is intended to help with the visually accurate reproduction of 1960s-era punched cards, mostly coinciding with my interest in the FORTRAN programming language. No attempt has been made to use historical BCD/EBCDIC encodings in these fonts. We have Unicode now.
The 029 card punch could not produce any bold or italic font variants, but FontForge can, so I did.
Things I learned in making these fonts
The 029 card punch printer could be damaged if you tried to print binary cards, as there was no way to disengage the code plate from the punch mechanism.
FontForge really hates to have paths in a glyph just touching. Either keep them more than one unit apart, or overlap them and merge the overlapping paths.
EBCDIC is weird.
Sources
Norbert Landsteiner’s amazing Punched Card Typography Explained page describes how the code plate system worked, and has JavaScript animations showing how characters were decoded (entirely mechanically) from the plate.
I suspect it’s comp.lang.fortran‘s second most frequently-asked question, but the language has no concept of stderr, the POSIX error output stream. Or at least, there’s no standard IO unit attached to stderr, if it’s defined at all.
Since writing to stderr is my usual debug message method, this is going to be a slog …
Just found one of my old Fortran-77 fractal programs, output of which is shown above. Reminds me of the days I used to consume (and ocasionally write for) Fractal Report avidly.
Being able to see this represents quite a bit of work on my part. It’s the output from WAsP‘s map editor, reprsenting some terrain roughness data exported from Surfer.
The original data set looks a bit like this:
It’s a grid of values. Unfortunately, WAsP wants the boundaries, and it took me a while to work out a (rather inefficient) algorithm to find them.
It’s very strange to be getting back into a language as different to Perl as it is possible to be. I’m fairly conversant with the weird bits of Perl — map, grep, hash usage, objects — but Fortran has a completely different toolkit
That’s not to say it’s a bad toolkit, just very different, F’rinstance, trying to find all the distinct values in an array. In Perl, you just walk through a hash, parallel to the array, incrementing each key for every value found. In Fortran — well, it’s a different story.