creyes1-FinalProject

creyes1 Final Project – Beat Machine

//Christopher Reyes
//creyes1@andrew.cmu.edu
//Section D
//Capstone Project

//This program is a reinterpretation of Patatap (www.Patatap.com) created by
//Jono Brandel (jonobr1.com) and Lullatone (www.lullatone.com).

//Animations and drumkit sounds play if a key is pressed on the keyboard
//To differentiate from Patatap, there is a beat loop functionality that
//records the users\'s actions, then plays them in sequence for beatmaking

//DIRECTIONS

//SPEAKERS ON
//Press SPACEBAR to begin loop recording, play drum sounds with KEYS 1-5,
//Press SPACEBAR AGAIN to stop recording and play recorded beat
//Press SPACEBAR TWICE to clear loop

//Note: Records/plays one loop at a time

var introTextX = 30; //Initalizes intro text position, sets to undefined to hide

//Color scheme - RGB values
var softYellow = [248, 249, 197];  //Background
var softPink = [252, 202, 199];    //For bigSlideH
var softViolet = [187, 170, 192];  //For bigSlideV
var lightCyan = [187, 252, 223];   //For rectSlide
var darkerTeal = [85, 200, 190];   //For ringPulse
var transWhite = [255, 255, 255, 100]; //Transparent white for bg rings

//Looping variables and arrays
var actions = []; //Lists actions performed
var actionFrames = []; //Frames on which actions were performed
var actionSounds = []; //Lists sounds to be played
var counter = 0; //Counts frames
var counterMax; //Cap on frame count, signals when to loop
var actionIndex; //Relationship between actions, actionFrames, actionSounds

//Drumkit sound samples: "Urban Drum Samples" by user Biochron of soundpacks.com
//Source Link: https://soundpacks.com/free-sound-packs/urban-drum-samples/
var snare;
var bass;
var hihat;
var hightom;
var lowtom;

function preload() {
    snare = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
            + "uploads/2017/12/creyes1-snare.wav");
    snare.setVolume(.3);
    bass = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
           + "uploads/2017/12/creyes1-bass.wav");
    bass.setVolume(1);
    hihat = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
            + "uploads/2017/12/creyes1-hihat.wav");
    hihat.setVolume(.6);
    hightom = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/"
              + "wp-content/uploads/2017/12/creyes1-hightom.wav");
    hightom.setVolume(.5);
    lowtom = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
             + "uploads/2017/12/creyes1-lowtom.wav");
    lowtom.setVolume(.5);
}

function setup() {
    createCanvas(480, 480);
    background(softYellow);
}

function draw() {
    background(softYellow);

    //Background detail, layered circles

    noStroke();
    fill(transWhite);
    ellipse(width/2, height/2, 400, 400);

    fill(softYellow);
    ellipse(width/2, height/2, 350, 350);

    stroke(transWhite);
    noFill();
    strokeWeight(10);
    ellipse(width/2, height/2, 320, 320);

    strokeWeight(5);
    ellipse(width/2, height/2, 500, 500);

    noStroke();
    fill(transWhite);
    ellipse(width/2, height/2, 200, 200);

    //Introductory Text & Instructions
    //Disappears once user begins playing

    noStroke();
    fill(255);
    rect(introTextX-10, 420, 440, 50, 10);

    fill(100);
    textStyle(BOLD);
    textFont("Courier New", 12);
    text('Press any key 1-5, press SPACEBAR to start/stop recording your loop. '
         + 'Double-tap SPACEBAR to stop loop. (Speakers up)',
         introTextX, 430, 450, 50);

    //Starts initial counter to track actions
    if (recording == true) {
        counter++;
    }

    //If in playback, and items are in array, play on a loop
    if (recording == false & actionFrames.length > 0) {

        counter++;

        //Loops counter after hitting max
        if (counter === counterMax) {
            counter = 0;
        }

        //Plays action if counter is the same as the frame which action occurred
        var success = false;
        for (i = 0; i < actionFrames.length; i++) {
            if (counter === actionFrames[i]) {
                success = true;
                actionIndex = i;
                break;
            }
        }

        //Plays the action if counter number matches the stored frame count
        if (success == true) {
            (actions[actionIndex])();
            (actionSounds[actionIndex]).play();
        }

    }

    //Functions check if anything is in their respective arrays, then draw
    playSlideH();
    playSlideV();
    playRectSlide();
    playRingPulse();
    playDotSpray();

    //Visual feedback

    //Red dot when recording
    if (recording == true) {
        noStroke();
        fill(204, 67, 67);
        ellipse(30, 30, 10, 10);
    }

    //Playback icon if looping with actions in array
    if (recording == false & actions.length > 0) {
        noStroke();
        fill(110, 142, 105);
        triangle(25, 25, 25, 35, 35, 30);
    }

}

var recording = false;

function keyPressed() {

    //Hides intro text once user begins playing
    if (keyCode == 32 || keyCode == 49 || keyCode == 50 ||
        keyCode == 51 || keyCode == 52 || keyCode == 53) {
        introTextX = undefined;
    }

    //Toggles a loop recorder
    if (keyCode == 32 & recording == false) {
        counter = 0;
        actions = [];
        actionFrames = [];
        actionSounds = [];
        recording = true;
        print("start");
    } else if (keyCode == 32 & recording == true) {
        counterMax = counter; //Creates a cap for counter to loop
        counter = 0;
        recording = false;
        print("stop");
    }

    //Triggers an animation and sound clip on press
    //If recording, stores action, the frame that action occurred, and the sound
    //into respective arrays - actions[], actionFrames[], actionSounds[]

    //Excluding dotSpray, clears arrays on press

    //Pressing 1 sprays dots while playing a highhat clip
    if (keyCode == 49) {
        hihat.play();
        dotSpray();
        if (recording == true) {
            print('1');
            actionFrames.push(counter);
            actions.push(dotSpray);
            actionSounds.push(hihat);
        }
    }

    //Pressing 2 slides two rectangles while playing a bass drum clip
    if (keyCode == 50) {
        bass.play();
        rectSliders = [];
        rectSlide();
        if (recording == true) {
            print('2');
            actionFrames.push(counter);
            actions.push(rectSlide);
            actionSounds.push(bass);
        }
    }

    //Pressing 3 creates a scaling ring while playing a snare clip
    if (keyCode == 51) {
        snare.play();
        rings = [];
        ringPulse();
        if (recording == true) {
            print('3');
            actionFrames.push(counter);
            actions.push(ringPulse);
            actionSounds.push(snare);
        }
    }

    //Pressing 4 slides a large panel horizontally, plays a hightom clip
    if (keyCode == 52) {
        hightom.play();
        bigSliderH = [];
        bigSlideH();
        if (recording == true) {
            print('4');
            actionFrames.push(counter);
            actions.push(bigSlideH);
            actionSounds.push(hightom);
        }
    }

    //Pressing 5 slides a large panel vertically, plays a lowtom clip
    if (keyCode == 53) {
        lowtom.play();
        bigSliderV = [];
        bigSlideV();
        if (recording == true) {
            print('5');
            actionFrames.push(counter);
            actions.push(bigSlideV);
            actionSounds.push(lowtom);
        }
    }

}

/*----Dot Spray Functions-----------------------------------------------------*/
//Dots fly out from a random position

var dots = []; //For dotSpray

//Places dots into array
function dotSpray() {
    var dotOriginX = random(0, width);
    var dotOriginY = random(0, height);
    for (var i = 0; i < 10; i++) {
        dots.push(makeDot(dotOriginX, dotOriginY));
    }
}

//Creates dot object
function makeDot(inputX, inputY) {
    var dot = {x: inputX,
               y: inputY,
               stepX: random(-20, 20),
               stepY: random(-20, 20),
               color: [random(0, 255), random(0, 255), random(0, 255)],
               size: random(5, 10),
               move: dotStep,
               display: drawDot}
    return dot;
}

//Render dot
function drawDot() {
    noStroke();
    fill(this.color);
    ellipse(this.x, this.y, this.size);
}

//Moves dot
function dotStep() {
    this.x += this.stepX;
    this.y += this.stepY;
}

//If something is in the array, execute animation
function playDotSpray() {
    //Checks if anything exists inside array
    if (dots.length > 0) {
        for (var i = 0; i < dots.length; i++) {
            //Moves and renders
            dots[i].move();
            dots[i].display();

            //If dot leaves canvas, remove it from dots array
            if (dots[i].x < 0 || dots[i].x > width ||
                dots[i].y < 0 || dots[i].y > height) {
                dots.splice(i, 1);
            }
        }
    }
}
/*----------------------------------------------------------------------------*/

/*----rectSlide Functions-----------------------------------------------------*/
//Two rectangles slide vertically across the screen, direction random

var rectSliders = []; //For rectSliders

//Places slider into array
function rectSlide() {
    var startingY = 480;
    var slideRate = 20;

    var sliderY1;    //Input for the first rectangle's Y position
    var sliderStep1; //Input for the first rectangle's move speed

    var sliderY2;    //Input for the second rectangle's Y position
    var sliderStep2; //Input for the first rectangle's move speed

    //50/50 chance on which direction the sliders travel
    var coin1 = coinToss();
    if (coin1 == true) {
        sliderY1 = -startingY;
        sliderStep1 = slideRate;
    } else {
        sliderY1 = startingY;
        sliderStep1 = -slideRate;
    }

    rectSliders.push(makeSlider(100, sliderY1, sliderStep1));

    var coin2 = coinToss();
    if (coin2 == true) {
        sliderY2 = -startingY;
        sliderStep2 = slideRate;
    } else {
        sliderY2 = startingY;
        sliderStep2 = -slideRate;
    }

    rectSliders.push(makeSlider(300, sliderY2, sliderStep2));
}

//Creates slider object
function makeSlider(inputX, inputY, inputS) {
    var slider = {x: inputX,
                  y: inputY,
                  w: 150,
                  h: 480,
                  color: lightCyan,
                  stepY: inputS,
                  move: slideStep,
                  display: drawSlider};
    return slider;
}

//Renders slider
function drawSlider() {
    noStroke();
    fill(this.color);
    rect(this.x, this.y, this.w, this.h);
}

//Moves slider
function slideStep() {
    this.y += this.stepY;
}

//If something is in the array, execute animation
function playRectSlide() {
    //Checks if anything exists inside array
    if (rectSliders.length > 0) {
        for (var i = 0; i < rectSliders.length; i++) {
            rectSliders[i].move();
            rectSliders[i].display();

            //If rectangle leaves canvas, remove it from array
            if (rectSliders[i].y < -500 || rectSliders[i] > 500) {
                rectSliders.splice(i, 1);
            }
        }
    }
}
/*----ringPulse Functions-----------------------------------------------------*/
//Creates a ring that grows or shrinks in size from center
var rings = [];

//Places ring into array
function ringPulse() {
    var scaleRate = 50;
    var startingSize; //Input for ring's starting size
    var scaleTick; //Input for ring scale rate
    var coin = coinToss();

    //Determines direrction ring is scaling
    if (coin == true) {
        //Ring explodes outwards
        startingSize = 0;
        scaleTick = scaleRate;
    } else {
        //Ring collapses inwards
        startingSize = width*1.5;
        scaleTick = -scaleRate;
    }

    rings.push(makeRing(startingSize, scaleTick));

}

//Creates ring object
function makeRing(inputSize, inputStep) {
    var ring = {x: width/2,
                y: height/2,
                size: inputSize,
                step: inputStep,
                color: darkerTeal,
                weight: 50,
                scale: ringStep,
                display: drawRing};
    return ring;
}

//Renders ring
function drawRing() {
    strokeWeight(this.weight);
    stroke(this.color);
    noFill();
    ellipse(this.x, this.y, this.size);
}

//Scales ring
function ringStep() {
    this.size += this.step;
}

//If something is in the array, execute animation
function playRingPulse() {

    if (rings.length > 0) {

        //Renders and scales ring
        for (var i = 0; i < rings.length; i++) {

            rings[i].scale();
            rings[i].display();

            //If ring gets too big or too small, remove it from array
            if (rings[i].size > width*2 || rings[i].size < 0) {
                rings.splice(i, 1);
            }
        }
    }
}
/*----------------------------------------------------------------------------*/

/*----bigSlideH Functions-----------------------------------------------------*/
//Slides a big block of color horizontally across the screen
var bigSliderH = [];

//Places slider into array
function bigSlideH() {
    var startingX = 580;
    var moveRate = 50;
    var sliderHX; //Input for X position of slider
    var sliderHS; //Input for movement rate of slider
    var coin = coinToss();

    //50/50 chance of coming in from either side of canvas
    if (coin == true) {
        sliderHX = -startingX;
        sliderHS = moveRate;
    } else {
        sliderHX = startingX;
        sliderHS = -moveRate;
    }

    bigSliderH.push(makeSliderH(sliderHX, sliderHS));
}

//Creates slider object
function makeSliderH(inputX, inputS) {
    var sliderH = {x: inputX,
                   y: 0,
                   w: width+100,
                   h: height,
                   speed: inputS,
                   col: softPink,
                   move: sliderHStep,
                   display: drawSliderH};
    return sliderH;
}

//Renders slider
function drawSliderH() {
    noStroke();
    fill(this.col);
    rect(this.x, this.y, this.w, this.h);
}

//Moves slider
function sliderHStep() {
    this.x += this.speed;
}

//If something is in the array, execute animation
function playSlideH() {
    if (bigSliderH.length > 0) {
        for (var i = 0; i < bigSliderH.length; i++) {
            bigSliderH[i].display();
            bigSliderH[i].move();

            //Clear array if sliders leave canvas
            if (bigSliderH[i].x < -580 || bigSliderH[i].x > 580) {
                bigSliderH = [];
            }
        }
    }
}
/*----------------------------------------------------------------------------*/

/*----bigSlideV Functions-----------------------------------------------------*/
//Slides a big block of color horizontally across the screen
var bigSliderV = [];

//Places slider into array
function bigSlideV() {
    var startingY = 580;
    var moveRate = 50;
    var sliderVY; //Input for Y position of slider
    var sliderVS; //Input for movement rate of slider
    var coin = coinToss();

    //50/50 chance of coming in from either side of canvas
    if (coin == true) {
        sliderVY = -startingY;
        sliderVS = moveRate;
    } else {
        sliderVY = startingY;
        sliderVS = -moveRate;
    }

    bigSliderV.push(makeSliderV(sliderVY, sliderVS));

}

//Creates slider object
function makeSliderV(inputY, inputS) {
    var sliderV = {x: 0,
                   y: inputY,
                   w: width,
                   h: height + 100,
                   speed: inputS,
                   col: softViolet,
                   move: sliderVStep,
                   display: drawSliderV};
    return sliderV;
}

//Renders slider
function drawSliderV() {
    noStroke();
    fill(this.col);
    rect(this.x, this.y, this.w, this.h);
}

//Moves slider
function sliderVStep() {
    this.y += this.speed;
}

//If something is in the array, execute animation
function playSlideV() {
    if (bigSliderV.length > 0) {
        for (var i = 0; i < bigSliderV.length; i++) {
            bigSliderV[i].display();
            bigSliderV[i].move();

            //Clear array if sliders leave canvas
            if (bigSliderV[i].y < -580 || bigSliderV[i].y > 580) {
                bigSliderV = [];
            }
        }
    }
}
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
//Returns a Boolean value with a 50/50 chance
function coinToss() {
    var coin = floor(random(0, 2));
    if (coin === 0) {
        return true;
    } else {
        return false;
    }
}

DIRECTIONS: Press number keys 1-5 to play various drum sounds, hit spacebar to record your beat, then spacebar once more to play it back – double-tap spacebar to clear your loop. A red dot in the upper-left indicates that you are recording, and an arrow indicates a loop is being played. Please turn on your speakers.

This program is a reinterpretation of Patatap created by Jono Brandel and Lullatone. While I really enjoyed my experience with Patatap, I felt that it was lacking a system to create a looping beat capability seen on percussion pads or Launchpads. Because of this, I wanted to focus less on creating a complete keyboard’s worth of complex animation and moreso on creating that kind of robust looping system.

The program works by having a running frame counter once recording begins, and storing the action with its respective animation and sound, as well as the frame that it was performed into separate arrays. To loop, the counter resets, and whenever the counter number is the same as a stored frame number, the associated action is executed. For me, the biggest challenge was having all of these arrays relate to each other and keeping everything organized for a clean loop, however once I figured out the core system down, it was just a matter of adding more content in terms of possible actions.

In the future, I’d like to develop this further to have a full keyboard’s worth of sound and animation, as well as being able to perform several loops at once (likely involving storing arrays within arrays), but for now, I’m really pleased with the final result. I’ve played percussion for several years, so it was really enjoyable to get the chance to translate that interest over to a different medium.

Sounds are from Urban Drum Samples on SoundPacks.com, by user Biochron.

creyes1-Project-12-Proposal


Text stroke included for clarity

For my final project, I was interested in experimenting with typography and, similar to the projects I cited in my Looking Outwards post for this week, giving a sense of motion and dynamics to a medium that’s intended to be static.

Taking this idea of fluidity in a literal sense, I’d like to create a piece where type that is given and edited by the user serves as vessels for water particles dropped from above, gradually filling the shapes and revealing the text. If a character is deleted, the particles remain, but drop into the water below, slightly raising the water level. The text itself would not be visible (stroke in the image is provided for clarity), and would rely on user input from the mouse to spawn particles to fill the shapes and give form to their thoughts, so to speak. The biggest challenges here would be to essentially build a loose physics engine from the ground up in a manner that could support having this many objects on-screen, as well as potentially developing a typeface specifically for this project to allow for contours to serve as barriers for the particles.

While not feasible for this class, but a thought for the future, I’d like to see this be a kind of collective journal across users – with the anonymity that the internet provides, it’d allow the user to send out their thoughts for others to fill and uncover, filling the pool underneath with the collective consciousness of its users.

creyes1-LookingOutwards-12


Process video of Generative Typography by John Oquist of IBUILDWORLDS

Created by John Oquist for the 35th Type Director’s Club Annual in 2015, Generative Typography features clouds of particles formed in the shape of typographic glyphs, to then be exerted on by some kind of force created by the program. What arises from this is a set of striking letterforms with a strong element of motion that is rarely seen in typography. The initial letter itself is created with a wireframe in the 3D modelling program, Blender, then populated with particles, and movement is added with the help of the particle system baked into the program itself.


In-program view of model deterioration

What I really love about this project is that the organic, fluid nature of the deformation against the rigidity of typography creates an incredibly compelling letterform, which I’d hope to echo in my own Final Project.

More of Oquist’s work can be found on BehanceDribbble, and the IBUILDWORLDS website.


Full alphabet of Buchstabengewitter

Similarly, but in a two-dimensional space, Buchstabengewitter created by designer and programmer Ingo Italic and Letters Are My Friends in 2012 warps letterforms in a different way, and focuses on the transition between letterforms.


Experiments of the Letters of My Friends opening animation using Buchstabengewitter

Created, animated, and morphed in vvvv, each glyph slowly bleeds into the next as the contour of the letterform is connected to the contour of the surrounding circle. The implementation seems fairly simple, but the effect that it creates is profound and engaging. However, I do wish that there was some variation in its execution since it seems fairly simple to implement – what other effects could be created, and is an internal typographic contour necessary to create these glyphs?


Static images of the full Buchstabengewitter alphabet

creyes1-Project-11-Composition

creyes1 Project-11 (Turtle Composition)

//Christopher Reyes
//Section D
//creyes1@andrew.cmu.edu
//Project-11 (Turtle Free Style)

var turtleV; //Vertical grid
var turtleH; //Horizontal grid

var vertOffset = 40;
var horOffset = 5;

var rectHeight = 0;

function setup() {
    createCanvas(480, 480);
    background(255);
    frameRate(60);
}

function draw() {

    //Background white
    noStroke();
    fill(255);
    rect(0, 0, width, height);

    //Blue sun
    noStroke();
    fill(211, 255, 250);
    ellipse(width/2, height/2, 200, 200);

    //Turtle Grid, changes color when close to cursor
    //vertical
    turtleV = makeTurtle(0, 0);
    turtleV.penDown();
    while (turtleV.x < width) {
        turtleV.vertical();
    }
    turtleV.penUp();

    //Horizontal
    turtleH = makeTurtle(0, 0);
    turtleH.penDown();
    while(turtleH.y < height) {
        turtleH.horizontal();
    }
    turtleH.penUp();

    //Curtain, slowly falls after loading
    noStroke();
    fill(229, 249, 247);
    rect(0, rectHeight, width, height);
    fill(192, 216, 214);
    rect(0, rectHeight, width, 50);
    fill(192, 216, 214);
    rect(0, rectHeight + 70, width, 10);
    rectHeight += 5;
}

//Vertical Turtle Grid, changes color when close to cursor
//From RGB(92, 93, 141) to RGB(153, 161, 166);
function verticalWave() {
    this.right(90);
    for (var i = 0; i < 479; i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 92, 153),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 93, 161),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 141, 166)];
        this.forward(1);
    }

    this.left(90);

    for (var i = 0; i < vertOffset+(i/10); i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 92, 153),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 93, 161),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 141, 166)];
        this.forward(1);
    }

    this.left(90);

    for (var i = 0; i < 479; i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 92, 153),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 93, 161),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 141, 166)];
        this.forward(1);
    }

    this.right(90);

    for (var i = 0; i < vertOffset+(i/10); i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 92, 153),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 93, 161),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 141, 166)];
        this.forward(1);
    }


}

//Vertical Turtle Grid, changes color when close to cursor
//From RGB(168, 198, 159) to RGB(204, 226, 163);
function horizontalWave() {
    for (var i = 0; i < 479; i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 168, 204),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 198, 226),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 159, 163)];
        this.forward(1);
    }
    this.right(90);
    for (var i = 0; i < horOffset+(i/10); i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 168, 204),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 198, 226),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 159, 163)];
        this.forward(1);
    }

    this.right(90);
    for (var i = 0; i < 479; i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 168, 204),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 198, 226),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 159, 163)];
        this.forward(1);
    }
    this.left(90);
    for (var i = 0; i < horOffset+(i/10); i++) {
        this.color = [map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 168, 204),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 198, 226),
                      map(dist(mouseX, mouseY, this.x, this.y), 0, 125, 159, 163)];
        this.forward(1);
    }
    this.left(90);
}


//Turtle Graphics Functions-----------------------------------------------------
function turtleLeft(d) {
    this.angle -= d;
}


function turtleRight(d) {
    this.angle += d;
}


function turtleForward(p) {
    var rad = radians(this.angle);
    var newx = this.x + cos(rad) * p;
    var newy = this.y + sin(rad) * p;
    this.goto(newx, newy);
}


function turtleBack(p) {
    this.forward(-p);
}


function turtlePenDown() {
    this.penIsDown = true;
}


function turtlePenUp() {
    this.penIsDown = false;
}


function turtleGoTo(x, y) {
    if (this.penIsDown) {
      stroke(this.color);
      strokeWeight(this.weight);
      line(this.x, this.y, x, y);
    }
    this.x = x;
    this.y = y;
}


function turtleDistTo(x, y) {
    return sqrt(sq(this.x - x) + sq(this.y - y));
}


function turtleAngleTo(x, y) {
    var absAngle = degrees(atan2(y - this.y, x - this.x));
    var angle = ((absAngle - this.angle) + 360) % 360.0;
    return angle;
}


function turtleTurnToward(x, y, d) {
    var angle = this.angleTo(x, y);
    if (angle < 180) {
        this.angle += d;
    } else {
        this.angle -= d;
    }
}


function turtleSetColor(c) {
    this.color = c;
}


function turtleSetWeight(w) {
    this.weight = w;
}


function turtleFace(angle) {
    this.angle = angle;
}


function makeTurtle(tx, ty) {
    var turtle = {x: tx, y: ty,
                  angle: 0.0,
                  penIsDown: true,
                  color: color(128),
                  weight: 1,
                  left: turtleLeft, right: turtleRight,
                  forward: turtleForward, back: turtleBack,
                  penDown: turtlePenDown, penUp: turtlePenUp,
                  goto: turtleGoTo, angleto: turtleAngleTo,
                  turnToward: turtleTurnToward,
                  distanceTo: turtleDistTo, angleTo: turtleAngleTo,
                  setColor: turtleSetColor, setWeight: turtleSetWeight,
                  face: turtleFace,
                  vertical: verticalWave,
                  horizontal: horizontalWave};
    return turtle;
}

I’m not as pleased with this project as I am with my previous ones, as I found myself constantly limited to the framerate – a lot of my ideas (turtle overlay sampling the color of each pixel of an image, varying thickness based on an underlying image by pixel, a much tighter grid than the one you see now) either caused the frame rate to tank, or got the program hung up in loading. Even so, I do like the idea I put forth here, and as I learn how to make more efficient code, potentially revisit this and make it what I had hoped it would turn out to be. Making this iteration wound up to be a lot of trial-and-error to more fully understand what was causing my previous iterations of this project to fail, so it definitely forced me to be more aware of the code that I was writing, and to consider what I was demanding out of the program.

creyes1-LookingOutwards-11


Brian Foo’s “Two Trains,” where the song varies depending on median household income for each neighborhood the 2 Train stops in

Created in 2016, Data-Driven DJ is a project by Brian Foo to create musical works through computation and interpretation of real-world data in new and experimental ways. The project is a collection of ten tracks, each sampling data from a broad swath of topics from social to cultural to environmental. Using a blend of programs such as ChucK (a programming language for real-time sound synthesis and music creation), Audacity, Python, Hydrogen (drum machine application), and Processing, each track that Foo creates is compelling and unique, and makes powerful statements when data is not only visualized in his videos, but synthesized into song. What I really like about Foo’s work is that while they may not be catchy, fleshed out songs, each track is fascinating and sends a powerful communication in regards to its subject. Even though the songs are essentially controlled by algorithm, Foo’s artistic touch is still incredibly evident, and the entire body of work shines for that reason.


Brian Foo’s “Rhapsody in Grey,” which uses brain wave data during a seizure to inform the song’s composition

Foo made his process for creating his tracks available as open-source on Github, and you can find more of him on Twitter, Facebook, Soundcloud, and Vimeo.

creyes1-Project-10-Landscape

creyes1 Project-10 (Generative Landscape)

//Christopher Reyes
//Section D
//creyes1@andrew.cmu.edu
//Project-10 (Generative Landscape)

//Colors
var green1 = [152, 186, 157];
var green2 = [169, 200, 172];
var green3 = [122, 153, 126];

//Shape Arrays
var orbs = [];
var cords = [];
var clouds = [];
var landlines = [];

var inc = 0; //Value for noise command

function setup() {

    createCanvas(480, 480);
    background(green1);
    noStroke();
    angleMode(DEGREES);

    //Generates random land form
    for (var x = 0; x < width; x++) {
        landlines.push(makeLandscape(x, 1));
    }

    //Draws initial set of orbs
    for (var i = 0; i < 5; i++) {
        var rx = random(width);
        var ry = random(height);
        orbs[i] = makeOrb(rx, ry);
    }

    //Adds cord properties to array
    for (var i = 0; i < 10; i++) {
        cords.push(makeCord());
    }


}



function draw() {

    background(green1);

    //Lighter background shapes
    fill(green2);
    noStroke();
    ellipse(width/2, height/2, 100, 300);
    ellipse(width/2 + 120, height/2, 75, 200);
    ellipse(width/2 - 120, height/2, 75, 200);

    //Renders and moves landscape
    for (var x = 0; x < landlines.length; x++) {
        landlines[x].display();
        landlines[x].move();
    }

    //Removes first in the array, or the line that goes off-canvas
    landlines.shift();

    //Increases increment by 1, pushes a new land line onto array
    inc += 1;
    landlines.push(makeLandscape(width-1, inc));

    //Layers lines and orbs for depth
    for (var i = 0; i < 7; i++) {
        cords[i].display();
    }

    updatePositions();

    for (var i = 7; i < 10; i++) {
        cords[i].display();
    }

    addOrb();
    addCloud();
}



//GENERAL FUNCTIONS-------------------------------------------------------------

function updatePositions() {

    //Update orb position and display
    for (var i = 0; i < orbs.length; i++) {
        orbs[i].move();
        orbs[i].display();
    }

    //Update cloud position and display
    for (var i = 0; i < clouds.length; i++) {
        clouds[i].move();
        clouds[i].display();
    }

}


//Removes orbs and clouds after passing edge of canvas
function removeFalloff() {

    var orbsToKeep = [];

    for (var i = 0; i < orbs.length; i++) {
        if (orbs[i].x + orb[i].breadth > 0) {
            orbsToKeep.push(orbs[i]);
        }
    }

    var cloudsToKeep = [];
    for (var i = 0; i < clouds.length; i++) {
        if (clouds[i].x) {
            cloudsToKeep.push(clouds[i]);
        }
    }

}

//ORB FUNCTIONS-----------------------------------------------------------------

//Orb & orb properties
function makeOrb(spawnPositionX, spawnPositionY) {

    var orb = {x: spawnPositionX,
               y: spawnPositionY,
               breadth: 216 * this.scale,
               scale: random(.25, 1),
               speed: random(-.75, -.15),
               angle: random(0, 360),
               move: orbMove,
               display: displayOrb}

    return orb;
}

//Base orb drawing, capabable of scaling and rotation
function displayOrb() {

    var orbSize = 216 * this.scale;
    var px = this.x-(orbSize/2);
    var py = this.y-(orbSize/2);

    //center position 108, 108
    //Inside pink shell rim
    fill(216, 154, 196);
    push();
    translate(px, py);
    rotate(this.angle);
    beginShape();
    vertex((53.8 * this.scale), (15.2 * this.scale));
    bezierVertex((1.781 * this.scale), (45.703 * this.scale),
                 -(15.078 * this.scale), (112.01 * this.scale),
                 (15.42 * this.scale), (163.31 * this.scale));
    bezierVertex((45.919 * this.scale), (214.601 * this.scale),
                 (112.226 * this.scale), (231.46 * this.scale),
                 (163.52 * this.scale), (200.96 * this.scale));
    bezierVertex((214.817 * this.scale), (170.462 * this.scale),
                 (231.676 * this.scale), (104.155 * this.scale),
                 (201.18 * this.scale), (52.86 * this.scale));
    bezierVertex((170.679 * this.scale), (1.564 * this.scale),
                 (104.372 * this.scale), -(15.295 * this.scale),
                 (53.8 * this.scale), (15.2 * this.scale));
    endShape();

    //Inside purple
    fill(158, 90, 146);
    beginShape();
    vertex(134.1 * this.scale, 40.76 * this.scale);
    bezierVertex(97.698 * this.scale, 46.405 * this.scale,
                 62.946 * this.scale, 83.298 * this.scale,
                 56.3  * this.scale, 126.13 * this.scale);
    bezierVertex(49.074 * this.scale, 172.708 * this.scale,
                 77.436 * this.scale, 207.524 * this.scale,
                 119.85 * this.scale, 200.15 * this.scale);
    bezierVertex(161.836 * this.scale, 192.853 * this.scale,
                 196.48 * this.scale, 147.78 * this.scale,
                 197.52 * this.scale, 103.01 * this.scale);
    bezierVertex(198.488 * this.scale, 61.779 * this.scale,
                 170.188 * this.scale, 35.198 * this.scale,
                 134.1 * this.scale, 40.76 * this.scale);
    endShape();

    //Inside purple shadow
    rectMode(CENTER);
    fill(116, 55, 107);
    push();
    translate((83.78 * this.scale), (93.85 * this.scale));
    rotate(30);
    rect(0, 0, 35 * this.scale, 175 * this.scale);
    pop();

    push();
    translate((108.09 * this.scale), (108.64 * this.scale));
    rotate(30);
    rect(0, 0, 6.5 * this.scale, 175 * this.scale);
    pop();

    //Large pink shell, counter-clockwise winding
    fill(194, 112, 173);
    beginShape();
        vertex((53.8 * this.scale), (15.2 * this.scale));
        bezierVertex((1.781 * this.scale), (45.703 * this.scale),
                     -(15.078 * this.scale), (112.01 * this.scale),
                     (15.42 * this.scale), (163.31 * this.scale));
        bezierVertex((45.919 * this.scale), (214.601 * this.scale),
                     (112.226 * this.scale), (231.46 * this.scale),
                     (163.52 * this.scale), (200.96 * this.scale));
        bezierVertex((214.817 * this.scale), (170.462 * this.scale),
                     (231.676 * this.scale), (104.155 * this.scale),
                     (201.18 * this.scale), (52.86 * this.scale));
        bezierVertex((170.679 * this.scale), (1.564 * this.scale),
                     (104.372 * this.scale), -(15.295 * this.scale),
                     (53.8 * this.scale), (15.2 * this.scale));

    //Ellipsoid cutout, clockwise winding
        beginContour();
            vertex((175.65 * this.scale), (50.62 * this.scale));
            bezierVertex((209.531 * this.scale), (65.455 * this.scale),
                         (222.219 * this.scale), (111.235 * this.scale),
                         (203.99 * this.scale), (152.87 * this.scale));
            bezierVertex((185.753 * this.scale), (194.502 * this.scale),
                         (143.504 * this.scale), (216.223 * this.scale),
                         (109.62 * this.scale), (201.38 * this.scale));
            bezierVertex((75.736 * this.scale), (186.544 * this.scale),
                         (63.049 * this.scale), (140.764 * this.scale),
                         (81.28 * this.scale), (99.13 * this.scale));
            bezierVertex((99.515 * this.scale), (57.497 * this.scale),
                         (141.764 * this.scale), (35.776 * this.scale),
                         (175.65 * this.scale), (50.62 * this.scale));
        endContour();

    endShape(CLOSE);

    //Shine
    fill(218, 179, 212);

    push();
    translate(  + (174.49 * this.scale),   + (117.35 * this.scale));
    rotate(25);
    ellipse(0, 0, 7 * this.scale, 30 * this.scale);
    pop();

    push();
    translate(  + (172.35 * this.scale),   + (140.86 * this.scale));
    rotate(25);
    ellipse(0, 0, 4 * this.scale, 17 * this.scale);
    pop();
    pop();
}

function orbMove() {
    this.x += this.speed;
}

//Occasionally adds an additional orb to the array
function addOrb() {
    var spawnChance = 0.005;
    if (random(0, 1) < spawnChance) {
        orbs.push(makeOrb(width*1.5, random(height)));
    }
}

//CORD FUNCTIONS----------------------------------------------------------------

//Line properties
function makeCord() {
    var cord = {x1: 0,
               x2: width,
               y1: randomGaussian(height*(2/3), 40),
               y2: randomGaussian(height*(2/3), 40),
               col: [218, 179, 212],
               weight: 2,
               display: drawCord}
    return cord;
}

//Draws line
function drawCord() {
    stroke(this.col);
    strokeWeight(this.weight);
    line(this.x1, this.y1, this.x2, this.y2);
    noStroke();
}

//CLOUD FUNCTIONS---------------------------------------------------------------

//Cloud properties
function makeCloud() {
    var cloud = {
        x: width*1.5,
        y: random(height),
        h: random(3*4, 12*4),
        w: random(40*4, 85*4),
        move: cloudMove,
        speed: random(-.5, -.1),
        col: [255, 255, 255, 100],
        display: drawCloud}
    return cloud;
}

//Draws cloud
function drawCloud() {
    noStroke();
    fill(this.col);
    ellipse(this.x, this.y, this.w, this.h);

    //ellipse(this.x + this.w*1.3, this.y - this.h*.25, this.w*.33, this.h*.33);
}

//Cloud movement
function cloudMove() {
    this.x += this.speed;
}

//Occasionally adds cloud to array
function addCloud() {
    var spawnChance = 0.004;
    if (random(0, 1) < spawnChance) {
        clouds.push(makeCloud());
    }
}

//LAND FUNCTIONS----------------------------------------------------------------

//Landscape properties
function makeLandscape(x, i) {
    var noiseScale = 0.002;
    var noiseVal = noise((x+i)*noiseScale);
    var landline = {x: x,
                    y1: height - 50 - noiseVal*100,
                    y2: height,
                    col: green3,
                    weight: 1,
                    speed: -1,
                    move: panLand,
                    display: drawLandscape}
    return landline;
}

//Draws individual lines for landscape
function drawLandscape() {

        stroke(this.col);
        strokeWeight(this.weight);
        line(this.x, this.y1, this.x, this.y2);

}

//Moves landmass
function panLand() {
    this.x += this.speed;
}

I wound up running into more difficulty than usual with this assignment, although it was definitely an interesting process when it came to problem-solving. I kept the main components of the program fairly simple – objects with random properties moving across the page. However, I really wanted to try to figure out how the landscape in the Flags assignment work, and after a lot of trial and error made a variation of it using moving vertical lines to piece together that far-off hillside. While the main objects weren’t so difficult to implement, the landscape took up a sizable chunk of my time, but I’m still happy with the result, plus it forced me to really understand the order in which functions were performed, as well as the little nuances of arrays and objects.

Analog & Digital Sketches:

creyes1-LookingOutwards-10


Video showing the navigation of the Friends in Space website

Created by Giorgia Lupi and Accurat studio, an information design firm, Friends in Space is an experimental digital social platform built to connect Italian astronaut Samantha Cristoforetti on the International Space Station with the world below her.


Sample view of the Friends in Space website, showing the user’s position and Cristoforetti’s orbit

From November 2014 to June 2015, with the help of data collection and processing, the site maps out the user’s location relative to Cristoforetti’s location on the I.S.S., allowing the two individuals to say hello should one be right above the other, as well as allowing users to send greetings to fellow stargazers. It’s a really heartfelt and charming way to use data visualization, and the project enables a kind of emotional bond between Cristoforetti and the people she passes by – users are able to plan out their calendars to make sure they don’t miss the next time Cristoforetti will pass over them, and are able to view all of their datapoints regarding their interactions.


Giorgia Lupi

Giorgia Lupi herself is an Italian information designer residing in New York City, and the co-founder and design director at Accurat studio, who is most known for her work in the collaboration Dear Data, a continuous exchange of hand-drawn data visualizations with Stefanie Posavic. Lupi received her Masters of Architecture at Ferrara Architecture Faculty in Ferrara, Italy, and earned a PhD in Design at Politecnico di Milano.

The Friends in Space project can be found on Behance, and more information about Lupi and Accurat can be found on their website, Behance, and their respective Medium blogs.

creyes1-Project-09-Portrait

creyes1 Project-09 (Portrait)

//Christopher Reyes
//Section D
//creyes1@andrew.cmu.edu
//Project-09 (Custom Pixel)

var underlyingImage;

function preload() {
    underlyingImage = loadImage('https://courses.ideate.cmu.edu/15-104/f2017/wp-content/uploads/2017/10/creyes1_15104pic.jpg');
}

function setup() {

    createCanvas(480, 480);
    background(255);

    underlyingImage.loadPixels(); //Loads pixel data
    frameRate(60);

}

function draw() {
    //Creates randomly spawning rectangles with color according to underlyingImage
    var px = random(width);
    var py = random(height);
    var ix = constrain(floor(px), 0, width-1);
    var iy = constrain(floor(py), 0, height-1);

    var theColorAtLocationXY = [red(underlyingImage.get(ix, iy)) + 20,
                                green(underlyingImage.get(ix, iy)),
                                blue(underlyingImage.get(ix, iy)),
                                70];

    var rectSize = random(10, 20);

    rectMode(CENTER);
    noStroke();
    fill(theColorAtLocationXY);
    rect(px, py, rectSize, rectSize*3);

    //Draws smaller squares for more detail...
    //At eyes
    drawDetailPixel(width/2, 50, 145, 200, 10);
    //At mouth
    drawDetailPixel(width/2 - 10, 50, 240, 300, 10);

}

//Samples underlying image data for pixel color, then draws a rectangle
//in a random position with a Gaussian distribution
function drawDetailPixel(xmin, xmax, ymin, ymax, pixelSize) {
    var dpx = randomGaussian(xmin, xmax);
    var dpy = random(ymin, ymax);
    var dix = constrain(floor(dpx), 0, width-1);
    var diy = constrain(floor(dpy), 0, height-1);
    var detailColXY = [red(underlyingImage.get(dix, diy)) + 20,
                                      green(underlyingImage.get(dix, diy)),
                                      blue(underlyingImage.get(dix, diy)),
                                      35];
    fill(detailColXY);
    rect(dpx, dpy, pixelSize, pixelSize);
}

//Draws a large transparent ellipse according to underlying image color
function mousePressed() {
    fill(red(underlyingImage.get(mouseX, mouseY)) + 20,
         green(underlyingImage.get(mouseX, mouseY)),
         blue(underlyingImage.get(mouseX, mouseY)),
         35);
    stroke(255, 255, 255, 35);
    strokeWeight(1);
    ellipse(mouseX, mouseY, random(150, 250));
}

While I didn’t run into too many issues with this project in terms of implementation, I did have some issues with finding a balance between keeping it visually interesting while avoiding having too much visual noise that it becomes confusing to look at. I really liked the idea that this was a program that would continue to loop and build upon itself, and so opted for various degrees of transparency so that the image would become more clear as the program ran. I also made it a point to highlight certain areas of the image, such as the eyes and mouth, with smaller transparent squares to build up some more defined shapes so that it would be easier to pick out a face from the constantly layering rectangles. I really like the interaction between the analog brush marks in the portrait with the computational pixels in the program, where it creates something almost painterly, yet not quite glitch art, but a really interesting in-between.


Original Self Portrait



Development of the portrait as the program runs and manual mouse clicks are introduced

creyes1-LookingOutwards-09


Screen capture from the Pictooptic website, showing icons for “Empathy”

I had recently stumbled upon my friend Yoonyoung Kim’s feature of Pictooptic, an extension of The Noun Project, an initiative to collect and celebrate the universal language of visual icons, with new icons being submitted to the site every day. With Pictooptic, icons that relate to the entered word are randomly assembled to create these visually fascinating Rorschach-like mirror images. Yoonyoung really enjoyed the “spontaneity and whimsical nature of the generator” and I feel that sums it up perfectly. The generator creates a wonderful blend of playful icons from creators the world over that allows the user to see the striking similarities and fascinating differences that come from condensing a word into a single icon.


Screen capture from the Pictooptic website, showing icons for “Charm”


An introductory video about The Noun Project

Yoonyoung’s Looking Outwards post can be found here, and more information about The Noun Project can be found on their website.

creyes1-LookingOutwards-08


Eyeo 2015 – Meejin Yoon from Eyeo Festival // INSTINT on Vimeo.

J. Meejin Yoon is a Korean-American architect and designer who is both a professor and head of the Department of Architecture at the Massachusetts Institute of Technology. With a specialty in designing for public spaces and utilizing emerging technologies in ways to encourage and facilitate interaction as well as crafting a specific user experience. Yoon received a Bachelor of Architecture from Cornell University in 1995, her Masters of Architecture in Urban Design with Distinction from Harvard University in 1997, as well as a Fulbright Fellowship to Korea in 1998.

What I love about Yoon’s work is that the technology she utilizes and integrates into her work is not simply a novelty, but is meant to be there and enhances the overall piece. Sometimes it’s more upfront as seen in Double Horizon at the Mexico-US border, which logs the movement of cars through various security checkpoints as ripples of light. Other times it’s subtle, such as in the Sean Collier Memorial at MIT, where there is no visible technology, but the amount of computational precision it took to create that structure with little to no support is staggering.



Sean Collier Memorial at MIT in Boston, MA (Top), and “Double Horizon” at San Ysidro Land Port of Entry near San Diego, CA (Bottom)

During her presentation, Yoon speaks frankly about her work and often mentions when her installations don’t go as planned with a smile, and recognizes that once her work is out in the public sphere, it now belongs to the public, and seeing how people interact with her work is a driving force for her. A lot of her work is very open-ended for people to approach and make the space their own, which is something I’m really interested in incorporating into my own work. It’s really interesting to see how people respond to these public installations, where the user can be as much of a creator as the person who put the installation there, which I think can be a powerful tool in creating these kinds of open-ended spaces. Additionally, her honesty and highlighting the off-beat moments of her process in her presentation helped further convey that idea of playfulness and trust, something that I deeply respect and would like to integrate into my own presentations where I discuss my work as a learning process rather than a shiny final product.


Yoon’s “Swing Time,” an interactive installation where swings lit up according to acceleration and movement, provided a lot of opportunities for play. While not intended to hold more than two people per swing, people would pack into these anyway as the installation permitted this kind of open-ended interaction – it helped that there was a bar next door.

More of Meejin Yoon’s work can be found on the website of her architecture and design firm co-founded by Eric Höweler, Höweler + Yoon, with additional information on the MIT Department of Architecture website.