Customization
Demofruit is written in Arduino, so if you want to customize it go ahead and use the information on the next couple of pages to install Arduino IDE and set it up for use with the Fruit Jam.
Texture Conversion
The Rotozoom demo tiles and spins a 128×128 pixel source image stored in texture.h as an RGB565 array.
Image requirements:
- 128×128 pixels (power-of-two - the demo wraps with a fast bitwise mask,
TEXTURE_MASK 0x7F, instead of division) - 24-bit uncompressed BMP (Windows/DIB), no alpha, no RLE
- Bold, high-contrast content — thick shapes, chunky patterns, or a strong central subject. Concentric shapes, mandalas, chunky pixel art, or a bold face/logo all work well.
Making the BMP
Any image editor works — Photoshop, GIMP, Affinity, Procreate, Pixelmator. Create or resize to 128×128 and export/save as BMP, 24-bit, uncompressed.
Converting the BMP to texture.h
The helper script, bmp2texture.py, bakes the image into the C header the sketch expects. It reads the BMP, handles its bottom-up row order, converts each pixel to RGB565, and writes the `texture[128][128]` array with the TEXTURE_W, TEXTURE_H, and TEXTURE_MASK defines.
python3 bmp2texture.py yourimage.bmp texture.h
The second argument is optional and defaults to texture.h. The script validates its input and will refuse anything that isn't a 128×128, 24-bit, uncompressed BMP — with a message telling you what to fix (wrong size, wrong bit depth, or compression enabled). Overwrite the project's texture.h with your converted version and recompile to change the rotozoom image.
Music
Each demo (and the menu) has its own soundtrack, played as a ProTracker MOD file. MODs are the native music format of the demoscene: a small bank of digitized instrument samples plus pattern data describing which sample plays at what pitch on each channel.
Playback uses pocketmod, an MIT-licensed MOD player, which renders the music in real time on core 0. The audio path is:
pocketmod mixes stereo float samples → converted to 16-bit → sent to the TLV320DAC3100 codec over I2S (via the RP2350's PIO + DMA).
Songs live in the sketch as C headers, one per slot:
Each header contains a MOD_DATA byte array; the sketch wraps each in its own namespace so they don't collide. pocketmod supports up to 32 channels, so 6-channel, 8-channel, and wider MODs play fine (not just 4-channel) — though authentic 4-channel MODs are the period-correct choice and mix with the least CPU. If a song fails to load, the sketch logs the failure and simply runs silent rather than crashing.
Converting your own MOD files
A helper script, mod2header.py, converts any ProTracker .mod file into the header format the sketch expects. It does a straight byte-to-array conversion — it does not alter the music.
python3 mod2header.py yoursong.mod song_4.h
The second argument is optional; if omitted it writes yoursong.h. To change a demo's music, convert your MOD directly to the matching song_N.h , then recompile. To change the menu music, target song_0.h.
Where to find MODs
- The Mod Archive filter by license and by file size (leaner tracks make smaller headers and faster compiles)
- Compose your own in a tracker such as MilkyTracker runs natively on macOS, Windows, and Linux
Notes on file size and editing
The bulk of a MOD's size is its sample data, not its pattern/song length. To shrink a header:
- In a tracker, the biggest lever is downsampling the largest samples in the Sample Editor (roughly halves their size per octave of rate reduction); trimming silence and tightening loops also helps.
- Shortening the order list (how many patterns play in sequence) plus deleting the now-unused patterns/samples reduces size only modestly, but is the right tool for fitting a track's *length* to a demo.
pocketmod loops a MOD automatically at its end, so a clean loop point often matters more than absolute length. Whatever you edit, listen to the loop seam (play to the end and let it wrap) before converting to a header.
Speaker Easter Egg
The onboard speaker port on the Fruit Jam is muted by default; the headphone jack always works. To enable the speaker: connect the A1 pad (GPIO41) on the GPIO header to GND with a jumper wire, then reset/power on the board.
To mute it: remove the jumper and reset.
The jumper is read once at startup (with an internal pull-up: tied to GND = speaker on, left open = muted), and the state is printed to the serial monitor. The speaker connector needs the board powered from 5 V to be audible.
Page last edited July 22, 2026
Text editor powered by tinymce.