DWITTER SON1K

1017 words ~ 5-10 minsMathieu 'p01' Henri on February 27th, 2017

DWITTER SON1K

DWITTER—SON1K, the winning entry of JS1k 2017 is a social AudioVisual LIVE coding environment for the Twitter generation.

In simple terms, DWITTER—SON1K is a very small code editor dedicated to produce Audio-Visual demos where you have 140 characters to write the Audio update function, another 140 characters to write the Visual update function, and a couple of helper methods to access a canvas and get some basic Math functions. At any time when authoring a demo, you can copy the URL from the address bar of you browser to share on social medias. Also it comes with an example Audio-Visual demo.

Inspiration

As the name suggest, this project was inspired by Dwitter which provides the same features and more albeit the sound support. The harsh constraint of 140 characters really drives creativity. It fell like a natural fit to add Audio support and share the result with the JS1k community.

Helpers methods

Adapting the concept to JS1k, I renamed the variable exposing the canvas and its context to the ones available in JS1k i.e. c and a respectively, and I added N = Math.random. The full list of helper methods and variables in DWITTER—SON1K is:

a = a 512x256 canvas
c = its 2D context
t = the time in seconds
S = Math.sin
C = Math.cos
T = Math.tan
N = Math.random
R = returns an rgba(...) string. Example call: R(140,140,140,.5)

How does it work ?

The goal was to create a social LIVE editor for AudioVisual demos that fits in 1024 bytes. The LIVE editor part itself was rather simple but it had to be able to share demos and come wit a decent sample demo to meet the requirements of JS1k

The render loop

On any input, the functions fa and fv are created using eval(/.../) with the value of the ia and iv inputs and a fixed function signature ( stored in the $ variable ) which passes the time in second and most of the other helper methods as agurment with a default value.

The fa method is called continuously by the audioprocess event of a scriptprocessor and fed with the audio timestamp to generate the Audio signal. Similarly the fv method is called continuously by a requestAnimationFrame and fed the time variable to generate the Visuals.

Handling broken code

Broken code happens a lot as a result of input even firing as the author writes in the two input fields.

The simplest way to handle this was to wrap the body of the functions fa and fv in a try{}catch(e){} block. This is not ideal but ensures that they are always valid functions.

Unfortunately the code can be evaluted as you type or edit a for() loop and lead to an infinite loop. To my knowledge there is no compact way to break out of infinite loops. The only way is to instrument any for() loops to impose a maximum iteration or running time but it is error prone without extracting the abstract syntax tree of the functions... and that alone is a whole challenge to do in 1024 bytes.

Shareable URL

On any input, the value of the ia and iv inputs are merged together and set as the fragment URL of the page. This allows the user to get the updated URL to copy and share on social networks.

On start, this process is done in reverse and falls back to a default string of code for the ia and iv inputs to gibe a default Audio-Visual demo in case the user opened DWITTER—SON1K from a blank URL.

Source code

Here is the uncompressed source code. Remember that is inlined in the shim of JS1k which gives a 2D canvas and leverage the browser playing field. As for the compression to fit in 1024 bytes, I used the undefeated RegPack.

// List of a arguments for the Audio and Visual update functions
$ = [
  `t = time_in_seconds`,
  `S = Math.sin`,
  `C = Math.cos`,
  `T = Math.tan`,
  `N = Math.random`
];

// Markup and explainations
a.insertAdjacentHTML(`afterend`, `<pre><b><h1>DWITTER—SON1K\nAudio Visual LIVE coding environment for the Twitter generation\nShare the current URL     🐦</h1>AUDIO\n<input maxlength=140 id=ia size=140>\n\nVISUAL\n<input maxlength=140 id=iv size=140>\n\n${$.join('\n')}\na = A 512x256 canvas\nc = Its 2D     context\nR = rgba-string method ex R(140,140,140,.5`);

// The R method to get rgba color strings
R = (r, g, b, a = 1) => `rgba(${r|0},${g|0},${b|0},${a})`;

// The default value of the Audio input
ia.value = atob(top.location.hash.slice(1)).split("\n")[0] || "return(N()*(q=Math.pow(1-t*2%1,8)+Math.pow(1-t/8%1,16)*8)+(t*(t&24|32)*[4,3,2,5,0,4,1,3][t*8&7]%1))    /32";

// The default value of the Visuals input
iv.value = atob(top.location.hash.slice(1)).split("\n")[1] || "for(d=f=300;d-=.5;c.fillRect(256+d*C(A),128+d*S(A),d/9+q*q,d/9))A=N()*6.3,c.fillStyle=R(f*S(A+t*3+S    (A*f/d+t)),f*S(A*5-t*3),d)";

// oninput, and start, update the fa and fv methods, and location.hash to get a sharable URL
(oninput = t => {
  fa = eval(`(${$})=>{try{${ia.value}}catch(e){}}`);
  fv = eval(`(${$})=>{try{${iv.value}}catch(e){}}`);
  top.location.hash = btoa(ia.value + "\n" + iv.value)
})($a = new AudioContext);

// Use a ScriptProcessor node for the Audio
// and requestAnimationFrame for the Visual 
with($a.createScriptProcessor(4096, ($f = t => {
  requestAnimationFrame($f, fv(t / 1000))
})($t = 0), 1))
  connect($a.destination), onaudioprocess = t => {
    for (t = t.outputBuffer.getChannelData(value = 0); value < 4096;)
      t[value++] = fa($t += 1 / $a.sampleRate)
  }

Holy moly, I won JS1k

If you know me, you probably heard about JS1k, the annual JavaScript competition run by kuvos since 2010, giving JavaScript developers a shim and 1024 bytes to do something cool. I love this competition and have entered most editions, always ranked in the Top 10. It feels good to finally get my JS1k badge of honor.

Don't worry this will not stop me from entering the next editions.

Feedback

I hope you appreciate this production. People seemed to like it. At least it was great fun for me to write and play with.

Don't hesitate to share your DWITTER—SON1K creations with me.