--- title: Recreating the Matrix rain with ANSI escape sequences date: 2025-12-21 layout: post project: true thumbnail: thumb_sm.png --- The Matrix digital rain implemented in raw C using ANSI escape sequences with zero dependencies—not even ncurses. This is a fork of Domsson's unique rendition of the Matrix rain: Fakesteak. Three years ago, I forked his project and added truecolor and Unicode support. I also drastically modified the algorithm to produce a rain that resembled the original aesthetic with high visual fidelity. ## Unicode support Unicode support in the 2022 version lacked flexibility. The charset used in the rain had to be a single contiguous block defined by `UNICODE_MIN` and `UNICODE_MAX` settings: ``` #define UNICODE_MIN 0x0021 #define UNICODE_MAX 0x007E static inline void insert_code(matrix *mat, size_t row, size_t col) { mat->code[index(mat, row, col)] = rand() % (UNICODE_MAX - UNICODE_MIN) + UNICODE_MIN; } ``` There was no way, for instance, to use both ASCII and Katakana at the same time. The user had to pick one. In the new version, the user can use any number of Unicode blocks using `glyphs` array. In fact, the default rain now includes both ASCII and half-width Katakana characters: ``` #define UNICODE(min, max) (((uint64_t)max << 32) | min) static uint64_t glyphs[] = { UNICODE(0x0021, 0x007E), /* ASCII */ UNICODE(0xFF65, 0xFF9F), /* Half-width Katakana */ }; static uint8_t glyphlen = (sizeof glyphs) / (sizeof glyphs[0]); static inline void insert_code(matrix *mat, size_t row, size_t col) { uint64_t block; uint32_t unicode_min, unicode_max; block = glyphs[(rand() % glyphlen)]; unicode_min = (uint32_t)block; unicode_max = (uint32_t)(block >> 32); mat->code[index(mat, row, col)] = rand() % (unicode_max - unicode_min) + unicode_min; } ``` Entries in the `glyphs` array are Unicode blocks bit-packed in an 8-byte container: the four low bytes forms the first codepoint and the four high bytes the last. ## Phosphor decay The dim afterglow of monochrome CRT displays is achieved by carefully scaling the RGB channels individually and mixing them: ``` #define DECAY_MPLIER 2 static inline void blend(matrix *mat, size_t row, size_t col) { unsigned char *color; color = mat->rgb[index(mat, row, col)].color; color[R] = color[R] - (color[R] - RGB_BG_RED) / DECAY_MPLIER; color[G] = color[G] - (color[G] - RGB_BG_GRN) / DECAY_MPLIER; color[B] = color[B] - (color[B] - RGB_BG_BLU) / DECAY_MPLIER; } ``` The blending function emulates the phosphor decay by gradually transitioning each raindrop's color towards the background color. The multiplier is the number of passes over the rain track needed before the afterglow disappears. ## The algorithm Nonetheless, the rain resembles the original with high visual fidelity. It's highly customizable and gentle on the CPU. On my 14" ThinkPad T490, which has a resolution of 1920x1080 and 4GHz CPU, it uses 2-3% of the CPU with occasional jumps of up to about 8%. Not too bad for a weekend project. The program has been tested with xterm and urxvt terminal emulators on OpenBSD and Arch Linux systems. Someone has managed to get it moving on a Raspberry Pi as well. Lastly, to compile and run: ``` $ cc -O3 main.c -o matrix $ ./matrix ``` "All I see is blonde, brunette, red head." Files: [source.tar.gz](source.tar.gz)