summaryrefslogtreecommitdiffstats
path: root/_log/_site/matrix-digital-rain.html
blob: 85bac5d7c780bfc67397fd9b67a638eb384ffb32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<p>The Matrix digital rain implemented in raw C using ANSI escape sequences with
zero dependencies—not even ncurses.</p>

<video style="max-width:100%;" controls="" poster="poster.png">
  <source src="matrix.mp4" type="video/mp4" />
</video>

<p>This project began over three years ago as a fork of Domsson’s unique rendition
of the Matrix rain: <a href="https://github.com/domsson/fakesteak" class="external" target="_blank" rel="noopener noreferrer">Fakesteak</a>.  I
aimed to modify the algorithm to produce a rain that resembled the original
with high visual fidelity.</p>

<h2 id="unicode-support">Unicode support</h2>

<p>Unicode support in the 2022 version lacked flexibility. The charset used in the
rain had to be a single contiguous block defined by <code class="language-plaintext highlighter-rouge">UNICODE_MIN</code> and
<code class="language-plaintext highlighter-rouge">UNICODE_MAX</code> settings:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#define UNICODE_MIN 0x0021
#define UNICODE_MAX 0x007E

static inline void insert_code(matrix *mat,
    size_t row, size_t col) 
{
    mat-&gt;code[index(mat, row, col)] = rand()
        % (UNICODE_MAX - UNICODE_MIN)
        + UNICODE_MIN;
}
</code></pre></div></div>

<p>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 <code class="language-plaintext highlighter-rouge">glyphs</code> array. In fact, the default rain now includes
both ASCII and half-width Katakana characters:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#define UNICODE(min, max)  (((uint64_t)max &lt;&lt; 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 &gt;&gt; 32);

    mat-&gt;code[index(mat, row, col)] = rand()
        % (unicode_max - unicode_min)
        + unicode_min;
}
</code></pre></div></div>

<p>Entries in the <code class="language-plaintext highlighter-rouge">glyphs</code> 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.</p>

<h2 id="phosphor-decay">Phosphor decay</h2>

<p>The dim afterglow of monochrome CRT displays is achieved by carefully scaling
the RGB channels individually and mixing them:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#define DECAY_MPLIER  2

static inline void blend(matrix *mat,
    size_t row, size_t col)
{
    unsigned char *color;

    color = mat-&gt;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;
}
</code></pre></div></div>

<p>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.</p>

<p>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.</p>

<p>Lastly, to compile and run:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cc -O3 main.c -o matrix
$ ./matrix
</code></pre></div></div>

<p>“All I see is blonde, brunette, red head.”</p>

<p>Files: <a href="source.tar.gz">source.tar.gz</a></p>