Dither is a programming language for creative coding.

Here's a quick taste of Dither's syntax:

include "std/math" include "std/time" include "std/vec" include "std/win" include "std/frag" // initialize window and context frag.init(win.init(200,200,win.CONTEXT_3D)) // ordered dither pattern lookup ord_dith_map := arr[i32,2]{ 0,48,12,60, 3,51,15,63; 32,16,44,28,35,19,47,31; 8,56, 4,52,11,59, 7,55; 40,24,36,20,43,27,39,23; 2,50,14,62, 1,49,13,61; 34,18,46,30,33,17,45,29; 10,58, 6,54, 9,57, 5,53; 42,26,38,22,41,25,37,21; }; // write your shader logic in dither shader := frag.program(embed (func( @builtin frag_coord : vec[f32,4], @varying uv : vec[f32,2], @uniform t : f32 ): vec[f32,4]{ c:= (frag_coord.xy as vec[i32,2])/2; u:= (uv-0.5)*5.0; o:= (math.sin(u.dot(u)**0.5*u.x+t*0.01)*0.35+0.5) d:= (ord_dith_map[c.y%8,c.x%8] as f32)/64.0 return {o<d,o<d,o<d,1.0}; }) as "fragment") // render loop while (1){ frag.begin(shader); frag.uniform("t", time.millis() as f32); frag.render(); frag.end(); win.poll(); }

But let's start from the basics! Here's a "hello world" program in Dither.

include "std/io" io.println("hello world from dither!")

You might find the syntax of Dither familiar if you came from C-like languages. In case you haven't noticed, you can modify example programs (like the one below) and run them by clicking the ▶ button. If you don't feel like typing, you can try out the Scratch-like, block-based editor.

include "std/io" func fact(x:i32):i32{ if (x) return x * fact(x-1); return 1; } x := 7 y := fact(x) io.println("factorial of %{x} is %{y}");

The standard libraries give us options when it comes to delivering pixels onto the screen, one of which allows you to draw things the easy way, à la Processing-style. Welcome back, pushMatrix()!

include "std/gx" gx.size(200,200); func tree(l:f32){ if (l < 8) return; gx.line(0,0,l,0); gx.translate(l,0); gx.rotate_deg(-15); for (i := 0; i < 2; i++){ gx.push_matrix(); tree(l*0.8); gx.pop_matrix(); gx.rotate_deg(30); } } while (1){ gx.background(0.9); gx.stroke(0.); gx.push_matrix(); gx.translate(100,200); gx.rotate_deg(-90); tree(42); gx.pop_matrix(); gx.poll(); }

Now let's make some sound! Below we play a chromatic scale by directly putting data into your sound card. (Click the little speaker button).

include "std/snd" include "std/math" rate := 10000 snd.init(rate,1); func note(freq:f32, sec:f32){ t := 0.0; while (t < sec){ if (!snd.buffer_full()){ x := math.sin( t * 2.0 * math.PI * freq ) env := math.sin( t * math.PI / sec); snd.put_sample(x*env); t += 1.0/rate; } } } for (i:=0; i<12; i++){ freq := 440 * 2**(i/12.0); note(freq, 0.25); }

We can also easily create GPU-accelerated 3D graphics that work across different platforms. Below we generate a simple terrain and render it from a camera. We can also combine it with the fragment module to create more interesting shading effects -- check out examples!

include "std/g3d" include "std/win" include "std/list" include "std/rand" W := 200; H := 200; nx := 20; nz := 20; g3d.init(win.init(W,H,win.CONTEXT_3D)); mesh := g3d.Mesh{}; for (i:=0; i<nz; i++){ for (j:=0; j<nx; j++){ h := rand.noise(i*0.2,j*0.2); mesh.vertices.push( {j-nx*0.5, h*10.0-5.0, i-nz*0.5} ); mesh.colors.push({0.5+h*0.5,h,0.5-h*0.5,1}); if (i&&j){ mesh.indices.push(i*nx+j); mesh.indices.push(i*nx+j-1); mesh.indices.push((i-1)*nx+j); mesh.indices.push(i*nx+j-1); mesh.indices.push((i-1)*nx+j-1); mesh.indices.push((i-1)*nx+j); } } } cam := g3d.Camera{} cam.look_at({0,15,15},{0,0,0},g3d.AXIS_Y); cam.perspective(45,W/(H as f32),0.1,100.0); frame := 0; while (1){ g3d.background(0.2,0.0,0.1); cam.begin(); mesh.draw(g3d.mat.rotate_deg(g3d.AXIS_Y,frame++)); cam.end(); win.poll(); }

Now that we've gotten a sneak peek of what Dither is capable of, feel free to give it a try in the online editor, download Dither, or check out even more examples!




Lingdong Huang, 2025-?, Future Sketches Group, MIT Media Lab