Ethan Printz
Performative Programming
A multi-user website built using NodeJS and WebSockets that translates code written live in P5 into a dramatic show for an audience.
Overview
Course
Designing Interfaces for Live Performance
Term
Spring 2020
Role
Solo Project - Design and Development

Ideation

Concept

This project involved three different types of users: the audience, the performers (people in the class), and me, the controller/composer. The performers each have their own custom-created web environment for programming and seeing the live output of a P5 sketch. Their code is sent to a central server where the outputs are combined together in interesting ways, with my input being used to refine the final product. The performance is done to music, where the server sends the amplitude/pitch as live variables to the clients to use as a basis for music visualization.
performative-programming-diagram.png

Development

Performer View / Code Editor

The first hurdle was simply creating a web environment that's able to support writing P5 code in real time with syntax highlighting and live output for both the sketch and overall performance. As such, I spent the first week of the project refining this component of the experience, and this is where I ended up:
image-3-1024x564.png
Getting P5 code to run arbitrarily on input event was quite a hassle. I initially tried a simple eval function with an accompanying canvas injection into the output div, but I could never get it to detect the P5 library alongside the user's script. Eventually I took a cue from P5 live and used an iframe to inject the scripts into its 'srcdoc' property. Here's the code for how that was accomplished.

<pre id="codeContainer" contenteditable="true">
      <code id="codeBlock" class="language-js">
      ...
      </code>
</pre>
...
<iframe id="output" sandbox="allow-same-origin allow-scripts" volume="0"></iframe>
function renderOutput(songName, songTime, songPlaying){
    // Save codeblock element
    let codeBlock = document.getElementById("codeBlock")
    // Get code of input without HTML element wrappers
    let codeBase = codeBlock.innerHTML.replace(/(<([^>]+)>)/ig,"")
        .replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
    // Apply to output iframe
    document.getElementById("output").srcdoc = replacementHTML
    // Try evaluating code to catch errors
    try{ eval(codeBase) }catch(error){ console.log("Code error") }
    // Return output code
    return codeBase;
}

The syntax highlighting was accomplished by Prism JS, though using it required quite a few workarounds as any time the syntax highlighting was run it would reset the cursor position of the corresponding contenteditable div back to the first character, so I pieced together a way to detect then change the cursor position each time the syntax highlighting is run.

Audience View / Code Execution

Next step– get the outputs to combine together in interesting ways and form a live feed to the audience. The primary concern here is performance: whether they're combined through four+ individual video feeds or streams of code as it changes, it would likely end up taxing for the audience's devices. After going down a rabbit hole of figuring out web video streaming, I decided to go with the decidedly simpler code stream. The audience client basically listens for a SocketIO event indicating a new code for a certain id, then updates the performer's respective frame to evaluate that new code:

// Upon receiving new performer
socket.on('performerConnected', id => {
    performers.push({id: id, codeBase: ''})
    // Log table of current performers
    console.table(performers);
    // Add iframe for performer
    $("#performers").append(`
    <iframe class="output" id="${id.split("#")[1]}" 
    sandbox="allow-same-origin allow-scripts">
    </iframe>`);
    // Add code overlay for performer
    $("#performersCode").append(`
    <div class="codeTile ${id.split("#")[1]}">
        <div class="codeUpdate"></div>
        <div class="idTag">${id}</div>
    </div>`);
});

socket.on('codeChange', ({id, codeBase}) => {
    socket.emit('songStateRequest', {audienceId: socket.id , performerId: id, requestTime: Date.now()});
    // Log code change
    console.log(`📦 ${id} has changed their code`)
    // If performer does not yet exist
    if(!performers.some(performer => performer.id == id)){
        performers.push({id: id, codeBase: codeBase})
        // Log table of current performers
        console.table(performers);
        // Add iframe for performer
        $("#performers").append(`
        <iframe class="output" id="${id.split("#")[1]}" 
        sandbox="allow-same-origin allow-scripts"
        srcdoc="${returnOutput("1", false, 0, codeBase, id.split("#")[1])}">
        </iframe>`);
        // Add code overlay for performer
        $("#performersCode").append(`
        <div class="codeTile ${id.split("#")[1]}">
            <div class="codeUpdate">${codeBase}</div>
            <div class="idTag">${id}</div>
        </div>`);
        // Set timeout to remove code overlay
        setTimeout(() => {
            $(`.${id.split("#")[1]} .codeUpdate`).html("");
        }, 2000);
    // If it does exist
    } else {
        // Update array listing
        performers[performers.findIndex(performer => performer.id == id)]['codeBase'] = codeBase;
        // Change iFrame srcdoc property
        document.getElementById(id.split("#")[1]).srcdoc = returnOutput("1", false, 0, codeBase, id.split("#")[1]);
        // Add code overlay
        $(`.${id.split("#")[1]} .codeUpdate`).html(codeBase);
        // Set timeout to remove code overlay
        setTimeout(() => {
            $(`.${id.split("#")[1]} .codeUpdate`).html("");
        }, 2000);
    }
});

User Testing

Performer Testing

Luckily I had plenty of friends willing to user test the performance– both from the performer's side and the audience's side. The first round of testing was primarily focused on the nature of the programmer/performer's interface: did they have the tools they needed to perform live? I found that starting them with just a bare p5 document didn't inspire much in the way of live/reactive code, mostly some static shapes even when I explicitly wrote that the live amplitude variable for the music should be used. I decided to change the starter/default to a pulsating circle that I pre-programmed as a starting point for the performers.

In-Class Demo

I was given a chance to demo this project in class, with two classmates, my professor, and myself being the performers while the rest of the class was in the audience. Here are some screen shots from that performance:
Screen Shot 2020-04-22 at 1.57.06 PM.png
Screen Shot 2020-04-22 at 2.02.55 PM.png