RECREATING THE MATRIX RAIN WITH ANSI ESCAPE SEQUENCES
21 DECEMBER 2025
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
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