651 words ~ 3-6 minsMathieu 'p01' Henri on October 16th, 2020


MONOSPACE: Flip dots with feelings, a JavaScript demo in 1021 bytes, winner of the 1024 bytes demo competition at Assembly 2020.

The Assembly is a demoscene event with the most anticipated 1kb, 4kb and full size demo competitions. I am absolutely thrilled to win, again.

This year, it has been difficult to find the mental energy to focus, but working on a creative project was a breath of fresh air. I began to work on MONOSPACE 4 months ago, going through prototypes and fine tuning many details, trying to bring a level of camera work never seen in 1k demos before.

Flip dots feelings in 1021 bytes

For MONOSPACE, the main inspiration came from plot writers and flip dots displays like the ones in train stations. After experimenting with speech synthesis in previous productions in 1kb and 256 bytes, I wanted to break the fourth wall. Another thing dear to me was to mimic a handheld camera that slightly shakes and goes out focus to increase the immersion in this monospace world.

Techincal tidbits

Unfortunately, Mozilla still hasn't fixed the performance bug 1190398 around ShadowBlur, filled for BLCK4777 5 years ago.

Therefore you need to use a Chromium or Webkit browser to enjoy MONOSPACE live. To watch the packed version, you might have to allow Audio/Sound in the Permisions or Site or Settings of your browser.

Otherwise, I recommend the video capture of the final result as that is often easier to watch than a production highly optimized for size and the hardware used for the competition.


On the technical side, MONOSPACE is compressed using the PNG bootstrapping technique I described multiple times. The hand minified code takes 1497 bytes before compression and fits into 818 bytes of DEFLATE stream + the PNG headers and the bootstrapper = 1021 bytes

Minified code

Here is the setup of MONOSPACE. It takes 59 bytes to provide a canvas and rendering context.

<canvas id="c"></canvas><script>b=c.getContext`2d`</script>

The minified code of MONOSPACE itself is 1438 bytes long.

d=[2280,1280,1520,c.width=1920,"0px MONOSPACE"],g=new AudioContext,o=g.createScriptProcessor(4096,,1),o.connect(g.destination),o.onaudioprocess=o=>{o=o.outputBuffer.getChannelData(e=Math.sin(t/16%1,m=Math.sin(Math.min(1,y=t/128)*Math.PI)**.5+.1,c.height=1080,b.shadowOffsetY=32420,"radial-gradient(#"+[222,222,222,222,155,155,102,102][t/16&7]+",black",b.font="920 32px MONOSPACE",f=[(x,y,t)=>x/y*2-t,(x,y,t)=>(x**2+y**2)**.5-t,(x,y,t)=>x/4^y/4-t,(x,y,t)=>y%x-t][t/16&3],u=""+[[,f,f," CAN YOU HEAR ME",f,f,,"MONOSPACE","THE END"][t/16|0]],t>n&&speechSynthesis.speak(new SpeechSynthesisUtterance(u,n+=16))));for(i=0;4096>4*i;i++)g[i]=r=(f(x=16-i%32,a=16-(i/32|0),t)/2&1)+(g[i]||0)/2,x+=o[0]/4+4*(1-m**.3)*Math.sin(i+t+8),a+=o[64]/4+4*(1-m**.3)*Math.sin(i+t),h=x*Math.sin(y*2+8)+a*Math.sin(y*2),p=4096/(m*32+4*h*Math.sin(e)+t%16),b.beginPath(f[i]=r/p),b.arc(h*Math.sin(e+8)*p+1280,x*Math.sin(y*2)*p-a*Math.sin(y*2+8)*p-31920,p>0&&p/(2+32-r*16),0,8),b.shadowBlur=o[0]**2*32+32-m*32+4+h*h/2,b.shadowColor="hsl("+[f(x,y,t)&2?t-a*8:180,(t&64)*m+"%",(t&64)*m+"%"],b.fill();b.shadowBlur=o[0]**2*32,b.shadowColor="#fee";for(i=0;4096>i;i++)o[i]=o[i]/2+((Math.sin(t*d[t/[4,4,4,4,1/4,1/4,16,4][t/16&7]&3]*Math.PI)*8+(t*d[t/8&3]/2&6)+t*d[t/16&3]/4%6)/64+f[i/4|0])*m,64>i&t%16*6>i&&b.fillText([u[i+(o[i]*2&1)]],i%9*32+o[0]*16+180,(i/9|0)*64+o[64]*16-t-31920),t+=1/g.sampleRate}

The goal is keep the heatmap of the DEFLATE stream as cool as possible which means that most characters take a few bits to endode, while a few charachters are red hot and take 8 or more bits.

Visual, Audio & Speech in a nutshell

The rendering uses 2D Canvas, abusing ShadowBlur to render everything with the depth of field.

The Audio uses scriptProcessingNode, abusing the onAudioProcess event to render the Audio and Visuals.

The Audio and Visuals feed each other to get perfect synch of the camera shake, depth of field and soundscape.

The speech synthesis, allows to speak out the function feeding the visuals, and the "feelings" of the flip dots display.

Chef kiss

Finally, yes MONOSPACE is named after the font family used to write the text, to gain a few bytes.

Additional links