q-o2-oscilation-public-address
clone your own copy | download snapshot

Snapshots | iceberg

No images in this repository’s iceberg at this time

Inside this repository

public-adress.js
application/javascript

Download raw (11.3 KB)


// thoughts process / where this came from:
// 0. because theme is public address - the interractives functionnalities on the website particularly matters (point of focus)
// 1. first idea was represent a crowd feeling on a website
//      * how our perception of sound in public place is influenced by the presence of others (layer of noise, heat, crowded or intimacy feeling)
//      * how can this be translated to a website (what its specificities?)
// 2. decided to have a shape that manifest the people presences on the website (when you connect you directly "feel" the presence of others)
// 3. decided to make it interactive - through the chat / or through an added buttons, so the audience can makes this shapes varies according to their experience of the live/sounds
//    it's like dancing or reacting to a sound in physical space, there is not only presence but actions/reactions
// 4. the shape should look/feel connected to sounds in design, but we want to avoid the classic waveform approach
//    so we went with a linguistic effect (bouba/kiki effect) as an inspiration, 
//    that speaks about how the human brain like to connect certain shapes to certain sounds, and has some cultural invariance
//    --> pointy shapes make "k" sounds
//    --> rounded shapes make "b" sounds
// 5. conclusion: 
//     a. the shapes number of points is the number of people connected, so it changes with people connecting/disconnecting
//     b. everybody can react to the point and make their point "dance" by switching it's behavior (pointy or rounded)
// 6. because the theme is public address, it then symbolicaly made sense to see this shape as a cable/link/network between the sounds, the artist and the audience
//    so it has 2 ends that does outside of the canvas, and if a lot of people are there it really feels like a dense network, with lot of connection


// WARNING:
// the way those path are encoded is PUNK
// this is why those curves looks COOL
// this is also why it makes no sense in term of svg specification
// there is overlayed points, mix of quadratic and cubic bezier, chain reactions, etc.

// TODO:
// if we use S, we end up with angular move at the begining and end correct that?


let svg = document.getElementById("sonic-crowd-network");
let path = document.getElementById("sonic-crowd-network-path");
let viewbox = 1000; // a square
let margins = 250;  // points don't go in those margins
let start_moves = 2;
let end_moves = 1;

// let number = 4;
// let number = 6;
// let number = 12;
// let number = 18;
// let number = 24;
// let number = 100;
let number = 200;

// let possible_move_types = ["L"];
// let possible_move_types = ["S"];
// let possible_move_types = ["T"];
// let possible_move_types = ["S", "T"];
let possible_move_types = ["L", "S", "T"];


// -- map a value to a new range
function map_value(value, min, max, new_min, new_max){
    return (((value - min) / (max - min)) * (new_max - new_min)) + new_min;
}

function mid_point(a,b){
    return [(a[0]+b[0])/2, (a[1]+b[1])/2];
}

// -- create a point in the viewbox with random position
function create_random_position(){
    let end_point = [Math.random(), Math.random()];
    end_point = end_point.map(x => map_value(x, 0, 1, margins, viewbox - margins));
    end_point = end_point.map(x => Math.round(x));
    return end_point;
}

function create_random_position_circle(){
    let radius = 200;
    let center = viewbox/2;
    let steps = 36;
    // let angle = Math.random() * 2 * Math.PI;
    let angle = Math.floor(Math.random() * steps) / steps * 2 * Math.PI;
    let end_point = [Math.cos(angle), Math.sin(angle)];

    end_point = end_point.map(x => (x * radius) + center);
    end_point = end_point.map(x => Math.round(x));

    return end_point;
}

function pick_random(arr){
    return arr[Math.floor(Math.random()*arr.length)];
}

function craft_move(type, end_point, next_end_point){

    // assign end control point according to move type
    let end_control_point = [];
    let values = [];

    switch(type) {
        case "L":
            // no need to pick start & end control point
            values = end_point;
            break;
        case "S":
            // IDENTITY TRICK 1:
            // the end control point is the next point in path order 
            // (we insert at the start to next is previously added)
            // the start control point is the symmetry of the last one
            // by chaining those it creates loops
            // --> because the end control of a point points toward the next point,
            // and the start control of a point points toward the previous point
            end_control_point = next_end_point;
            values = end_control_point.concat(end_point);
            break;
        case "T":
            // IDENTITY TRICK 2:
            // we chain two T moves as one
            // by chaining those it creates explosions, because (from the spec):
            // The control point is a reflection of the control point of the previous curve command. 
            // If the previous command wasn't a quadratic Bézier curve, 
            // the control point is the same as the curve starting point (current point).
            // --> this create a chain reaction where all their control point are reflections
            // until it reaches the first one (a C move - not quadratic), so they're all reflection
            // of the first start point after the first C move.
            values = end_point.concat(end_point);
            break;
        default:
            console.log("Error: unrecognised move type");
    }

    let move = {
        type: type,
        values: values
    }

    return move;
}

// -- create a new move based on random
function create_random_move(next_end_point){

    // assign random position
    let end_point = create_random_position();
    // let end_point = create_random_position_circle();

    // assign random move type
    let type = pick_random(possible_move_types);

    // create the values according to type and positions
    let new_move = craft_move(type, end_point, next_end_point);

    return new_move;
}

// -- parse path data
function parse_path(){

    // get back d attribute and parse/split
    let path_data = path.getPathData();

    // we chained two T moves as one so we group them two by two
    let real_path_data = [];
    for (let i = 0; i < path_data.length; i++) {
        let type = path_data[i].type;
        if (type == "T"){
            // duplicate values
            path_data[i].values = path_data[i].values.concat(path_data[i].values);
            real_path_data.push(path_data[i]);
            // skip the next
            i = i+1;
        } else{
            real_path_data.push(path_data[i]);
        }
    }

    return real_path_data;
}


// -- add a new move,
// for when somebody connect to the website
function add_move(){

    // get back d attribute and parse/split
    let path_data = parse_path(path);

    // let start_path_data = path_data.slice(0, -1);             //all move but not the last one
    // let last_move = path_data[path_data.length - 1];          //the last move
    // let previous_move = start_path_data[start_path_data.length - 1];
    // let previous_end_point = previous_move.values.slice(-2);

    let first_moves = path_data.slice(0,2);
    let end_path_data = path_data.slice(2, path_data.length);
    let previous_added_move = end_path_data[0];
    let previous_added_end_point = previous_added_move.values.slice(-2);
    
    // create new move
    let new_move = create_random_move(previous_added_end_point);
    
    // add the move and update svg
    // start_path_data.push(new_move);
    // start_path_data.push(last_move);
    
    path_data = first_moves.concat([new_move], end_path_data);
    path.setPathData(path_data);

    console.log(path_data);
}

// -- remove the i move,
// for when somebody disconnect of the website
// NOTE: this create discontinuity with S type move
// because the anchor of the previous than the one removed
// will point to nowhere...
function remove_move(i){

    // get back d attribute and parse/split
    let path_data = parse_path(path);
    let number_of_moves = path_data.length - (start_moves+end_moves);
    let j = start_moves + i;

    // splice to remove elem i
    if(i < number_of_moves){
        path_data.splice(j,1);
    }
    
    // write back to svg
    path.setPathData(path_data);
}

function remove_random_move(){
    let path_data = parse_path(path);
    let number_of_moves = path_data.length - (start_moves+end_moves);
    let i = Math.floor(Math.random()*number_of_moves);
    remove_move(i);
}


// -- switch the type of move i
function switch_move(i){

    // get back d attribute and parse/split
    let path_data = parse_path(path);
    let number_of_moves = path_data.length - (start_moves+end_moves);
    let j = start_moves + i;

    if(i < number_of_moves){
        // create the new move
        let move = path_data[j];
        let type = move.type;
        let end_point = move.values.slice(-2);

        let next_move = path_data[j+1];
        let next_end_point = next_move.values.slice(-2);

        let new_possible_move_types = possible_move_types.filter(t => t != type);
        let new_type = pick_random(new_possible_move_types);

        let new_move = craft_move(new_type, end_point, next_end_point);

        // replace old move by new move
        path_data.splice(j,1,new_move);

        // write back to svg
        path.setPathData(path_data);
    }
}

function switch_random_move(){
    let path_data = parse_path(path);
    let number_of_moves = path_data.length - (start_moves+end_moves);
    let i = Math.floor(Math.random()*number_of_moves);
    switch_move(i);
}

// -- init the path 
// as a line with the two ends points and anchors
function init_path(){
    
    let tpoint = [viewbox/2, 0];

    // option 1: then the explosion is centered (at least the first one)
    // let mpoint = [viewbox/2, viewbox/2];
    // option 2: the first explosion center is random (= this point)
    let mpoint = create_random_position_circle();

    let bpoint = [viewbox/2, viewbox];
    let anchor_length = viewbox/2;

    let init_move = {
        type: "M",
        values: tpoint
    }
    let start_move = {
        type: "C",
        values: [tpoint[0], tpoint[1] + anchor_length,   //start control point
                 mpoint[0], mpoint[1],                   //end control point (=null)
                 mpoint[0], mpoint[1]]                   //end point
    }
    let last_move = {
        type: "C",
        values: [mpoint[0], mpoint[1],                    //start control point (=null)
                 bpoint[0], bpoint[1] - anchor_length,    //end control point
                 bpoint[0], bpoint[1]]                    //end point 
    }

    let moves = [init_move, start_move, last_move];
    path.setPathData(moves);

}

function save_svg(){

    let path_data = parse_path(path);
    let number_of_moves = path_data.length - (start_moves+end_moves);

    let fileContent = svg.outerHTML;
    let bb = new Blob([fileContent ], {type: "image/svg+xml"});
    let a = document.createElement('a');
    a.download = 'PA_'+possible_move_types.join("-")+'_'+number_of_moves+'.svg';
    a.href = window.URL.createObjectURL(bb);
    a.click();
}


// -- create a random path of n move (+ start and end)
function random_path(n){
    init_path();
    for (let i = 0; i < n; i++) {
        add_move();
    }
}

// init_path();
random_path(number);