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 not 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")
Leave a Reply