Skip to content

creative-coding

A Candid Conversation with my Artificial Self

Using GPT-3 to get to the brass tacks of humanity in an AI world

🗒️
This was created in August 2020! I'm retrieving some of my favorite old works and migrating them to the website.

The World's First AR Rapper

Look out for M.P. in the XXL Freshman 2021

🗒️
This was created in September 2020! I'm retrieving some of my favorite old works and migrating them to the website.

My 27th Birthday Party

Inviting all my closest friends to my 27th

🗒️
This was created in January 2021! I'm retrieving some of my favorite old works and migrating them to the website.
0:00
/

I Must Find A Place For Their Souls To Rest

I'm done with the feelings I've learned to know in paradox

Results

https://bram-adams.ghost.io/content/images/2023/08/2023.08.10-21.20.38.png
2023.08.10-21.20.38.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.10-21.20.16.png
2023.08.10-21.20.16.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.10-21.15.51.png
2023.08.10-21.15.51.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.10-21.14.38.png
2023.08.10-21.14.38.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.10-21.11.18.png
2023.08.10-21.11.18.png

Process

Inspiration (Spoiler Alert for Vinland Saga!)

https://bram-adams.ghost.io/content/images/2023/08/i-must-carry-them-to-a-place-they-can-rest.png
i must carry them to a place they can rest.png

  1. Generate background image with DALL-E
  2. Run the following code

Code

const canvasSketch = require("canvas-sketch");
const p5 = require("p5");

let img;

const preload = (p5) => {
  img = p5.loadImage(
    "assets/DALL·E 2023-08-10 20.56.53 - i must find a place for them to rest.png"
  );
};

const settings = {
  // Pass the p5 instance, and preload function if necessary
  p5: { p5, preload },
  // Turn on a render loop
  animate: false,
};

canvasSketch(({ p5 }) => {
  function rectanglePortion(img, x, y, width, height) {
    let numRows = 15;
    let totalWidth = width - 40; // Subtracting 40 to add some padding
    let rowHeight = height / numRows;

    // Loop through each row
    for (let i = 0; i < numRows; i++) {
      let x = 20; // Starting x position for rectangles
      let y = i * rowHeight; // y position based on current row
      let remainingWidth = totalWidth;
      p5.noStroke()

      // Randomly create rectangles until the row is filled
      while (remainingWidth > 0) {
        let rectWidth = p5.random(20, remainingWidth); // Random width, but not more than the remaining space
        let rectHeight = p5.random(rowHeight * 0.5, rowHeight); // Random height within the row

        // choose random opacity from 0.6 to 1.0
        let opacity = p5.random(0.4, 0.8);

        // Set the fill color
        p5.fill(255, opacity * 255);

        // Draw the rectangle
        p5.rect(x, y + (rowHeight - rectHeight) * 0.5, rectWidth, rectHeight);

        // draw a series of small rectangles up to height of the row
        for (let j = 0; j < 10; j++) {
            p5.noStroke()
            let rectWidth = 1;
            let rectHeight = p5.random(rowHeight * 0.5, rowHeight); // Random height within the row

            // choose random opacity from 0.6 to 1.0
            let opacity = p5.random(0.4, 0.8);

            // Set the fill color

            p5.fill(p5.color("rgb(220,20,60)"), opacity * 255);

            // Draw the rectangle
            p5.rect(x + p5.random(1, 15), y + (rowHeight - rectHeight) * 0.5, rectWidth, rectHeight);
            x += rectWidth;
            remainingWidth -= rectWidth;
        }


        if (p5.random() > 0.5) {
            // draw parallel lines through the rectangle
            p5.stroke(0, opacity * 255);
            p5.strokeWeight(1);
            p5.line(x, y + (rowHeight - rectHeight) * 0.5, x + rectWidth, y + (rowHeight - rectHeight) * 0.5 + rectHeight);
        }

        // Update x position for next rectangle, and the remaining width
        x += rectWidth;
        remainingWidth -= rectWidth;
      }
    }
  }
  return ({ p5 }) => {
    p5.createCanvas(1000, 1000);
    p5.background(255);
    p5.image(img, 0, 0);
    rectanglePortion(img, 0, 0, 1000, 1000);
  };
}, settings);

bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.

Embeddings as Shapes v0.1

Embeddings, this time as shapes! (v0.1)

Similar concept to visualizing embeddings vectors with colors.

Current issue is how to treat scale and drop shadow! I want the higher values to "pop" out of the canvas, but so many values are close to each other to be indiscernible value gradients. Workshopping it!

https://bram-adams.ghost.io/content/images/2023/08/2023.08.09-13.38.22.png
2023.08.09-13.38.22.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.09-13.34.33.png
2023.08.09-13.34.33.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.09-13.32.50.png
2023.08.09-13.32.50.png
https://bram-adams.ghost.io/content/images/2023/08/2023.08.09-13.29.58.png
2023.08.09-13.29.58.png

Code

const min = Math.min(...embedding);
const max = Math.max(...embedding);

const settings = {
  // Pass the p5 instance, and preload function if necessary
  p5: { p5 },
  // Turn on a render loop
  animate: false,
};

canvasSketch(({ p5 }) => {
  return ({ p5 }) => {
    // create a grid of squares from embeddings.length
    // assign each square a color from the embedding value

    // scale shapes from min and max values from 0 to 1
    function scaleShapes(value) {
        return ((value - min) / (max - min)) * 10;
    }   

    const padding = 10;

    p5.createCanvas(480, 320);

    const scaledShapes = embedding.map(scaleShapes);

    // return index of min and max scaledShapes
    const minIndex = scaledShapes.indexOf(Math.min(...scaledShapes));
    const maxIndex = scaledShapes.indexOf(Math.max(...scaledShapes));
    console.log(minIndex, maxIndex);
    console.log(scaledShapes[minIndex], scaledShapes[maxIndex]);

    // create a grid of squares from embeddings.length
    for (let i = 0; i < 48; i++) {
        for (let j = 0; j < 32; j++) {
            const index = i * 32 + j;
            // p5.noStroke();            
            // put a rect in middle of padding index
            // p5.rectMode(p5.CORNER);
            // p5.rect(i * 10, j * 10, 10, 10);

            // if embedding value at index is negative, fill with black
            if (embedding[index] < 0) {
                p5.fill(0);
                // add a drop shadow
                p5.drawingContext.shadowOffsetX = 2 + scaledShapes[index];
                p5.drawingContext.shadowOffsetY = 2 + scaledShapes[index];
                p5.drawingContext.shadowBlur = 2;
                p5.drawingContext.shadowColor = "rgba(0,0,0,0.2)";
            } else {
                p5.fill(255);
                // remove drop shadow
                p5.drawingContext.shadowOffsetX = 0;
                p5.drawingContext.shadowOffsetY = 0;
                p5.drawingContext.shadowBlur = 0;
                p5.drawingContext.shadowColor = "rgba(0,0,0,0)";

            }

            p5.noStroke();
            p5.rectMode(p5.CENTER); 

            p5.rect(i * 10 + (padding / 2), j * 10 + (padding / 2), 
            5 + scaledShapes[index], 
            5 + scaledShapes[index]);
        }
    }
  };
}, settings);

bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.

Embeddings Visualized as Colors

Mixing p5 and embeddings!

Wouldn't it be cool to not only visualize embeddings in a 2d space, but also be able to see the differences between them?

Here's a p5 experiment that does just that! I convert embeddings to colors and then place each of the 1536 vector numbers next to one another. The results are pretty neat!

It seems that a few indexes of the vectors stay strongly constant no matter what quote I pass in. These are visualized below by the strong yellow, pink, and purple cells. Why might this be the case?

Images Generated by Code

https://bram-adams.ghost.io/content/images/2023/08/2023.08.08-12.40.41.png
2023.08.08-12.40.41.png

generated by...

According to Seneca, God (think Jupiter) sets us back not to punish us but to give us an opportunity to do something courageous and thereby increase our chances of attaining “the highest possible excellence.” God, Seneca explains, hardens, reviews, and disciplines those who have won his approval and love; but those whom he seems to favor, whom he seems to spare, he is keeping soft against the misfortunes that are to come. You are wrong if you think anyone has been exempted from ill; the man who has known happiness for many a year will receive his share some day; whoever seems to have been set free from this has only been granted a delay We should therefore be flattered if we encounter setbacks. Paradoxically, it is evidence that we have caught the attention of God—indeed, that he regards us as a candidate for achieving human excellence. God, says Seneca, knows that “a man needs to be put to the test if he is to gain self-knowledge” and that “only by trying does he learn what his capacities are.”
https://bram-adams.ghost.io/content/images/2023/08/2023.08.08-12.40.01.png
2023.08.08-12.40.01.png

generated by...

Estimates are corporate currency that trade right up the ladder. If your team refuses to provide you estimates, you can bet that your peers (competitors, if you’re an opportunist) aren’t having the same struggles. Do you want to be the only one without estimates for your boss, who wants her aggregate estimates from all of the dev managers? Do you want her to be sitting in a meeting with her boss, with a single miss on her estimate sheet where her competitors have all of their estimates in? Someone somewhere in the food chain is making a call based on all of those estimates, and that call is a pure matter of self-interest. That person wants to make a bold prediction or strategic play and turn out to be right. Everyone below them in the food chain needs to furnish the best possible information to make this as likely as possible. Good estimates and predictions are how you scratch a superior’s back, with the implicit promise that the superior will then scratch yours. Do you notice something interesting here? If this were a game of musical chairs, then when the music stopped, the line-level developer would be standing. Everyone else in the corporate hierarchy trades in guesses and predictions, strategy and machinations, orders and instructions. The developers are the only ones that trade in actual output. Theirs is the only tangible contribution to the whole pyramid. And significantly, theirs is the only narrative that cannot easily be spun. Being defined by output rather than spun narrative is the essence of the delivery trap.
https://bram-adams.ghost.io/content/images/2023/08/2023.08.08-13.09.11.png
2023.08.08-13.09.11.png

generated by...

“A lie told once remains a lie, but a lie told a thousand times becomes the truth.”

Code

const embedding = [0.002166514,-0.01843581,-0.0026706716,...] // 1536 from ada-embedding

const min = Math.min(...embedding);
const max = Math.max(...embedding);

const settings = {
  p5: { p5 },
  animate: false,
};

canvasSketch(({ p5 }) => {
  return ({ p5 }) => {
    // create a grid of squares from embeddings.length
    // assign each square a color from the embedding value

    function mapToRGB(value) {
      return ((value - min) / (max - min)) * 255;
    }

    const rgbValues = embedding.map(mapToRGB);

    const extractRGBValues = (rgbString) => {
      const rgb = rgbString.replace(/[^\d,]/g, "").split(",");
      return rgb.map((value) => parseInt(value));
    };

    const colors = [];
    for (let i = 0; i < rgbValues.length; i += 3) {
      const r = Math.round(rgbValues[i] || 0);
      const g = Math.round(rgbValues[i + 1] || 0);
      const b = Math.round(rgbValues[i + 2] || 0);
      colors.push(`rgb(${r}, ${g}, ${b})`);
    }

    p5.createCanvas(512, 512);
    p5.background("#ffffff");
    p5.noStroke();
    p5.drawingContext.setLineDash([0.7, 17]);

    // convert the 512 color values into a grid of squares of 16 x 32
    for (let i = 0; i < 32; i++) {
      for (let j = 0; j < 16; j++) {
        const index = i * 16 + j;
        p5.fill(colors[index]);
        // if color at colors[index] is less than 128,128,128 add a stroke line around the square -- get # from rgb string ex.rgb(196, 190, 191)

        if (extractRGBValues(colors[index]).some((value) => value < 187)) {
          // random 50% white, 50% black stroke
          p5.stroke(p5.random([0, 255]));
          // p5.stroke(0);
          p5.strokeWeight(1.4);
        } else {
          p5.noStroke();
        }
        p5.rect(i * 16, j * 32, 16, 32);
      }
    }
  };
}, settings);

bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.

Old Man and the Noi(sea)

when the voices in your head are noiSY!

0:00
/
0:00
/
0:00
/

Code

source

const canvasSketch = require("canvas-sketch");
const p5 = require("p5");
const perspective = require("perspective-transform");

let img;
let THE_SEED;
const number_of_particles = 5000;
const number_of_particle_sets = 7;
let particle_sets;

const squeeze_y = 0.2;
const perspective_x = 0.9;

let pTransform;

let tick;
const print_time = 2000;

const preload = (p5) => {
  img = p5.loadImage(
    "assets/bramses_On_the_museum_wall_this_evocative_excerpt_invites_the_v_72996f7c-18e3-4bff-9358-16c921ab802a.png"
  );
};

const settings = {
  // Pass the p5 instance, and preload function if necessary
  p5: { p5, preload },
  // Turn on a render loop
  animate: true,
};

canvasSketch(({ p5 }) => {
  class Particle {
    constructor(x, y, phi, img) {
    
      this.pos = p5.createVector(x, y);
      this.angle = phi;
      this.val = 0;
      this.img = img;
    }

    update(index) {
      this.pos.x += p5.cos(this.angle);
      this.pos.y += p5.sin(this.angle);

      let nx = 1.3 * p5.map(this.pos.x, 0, p5.width, -1, 1);
      let ny = 1.3 * p5.map(this.pos.y, 0, p5.height, -1, 1);

      let np = pTransform.transformInverse(nx, ny);

      let n = p5.createVector(nx, ny);

      this.altitude =
        p5.noise(n.x + 423.2, n.y - 231.1) +
        0.04 * p5.noise(n.x * 15 - 113.3, n.y * 8 + 261.1) +
        0.02 * p5.noise(n.x * 30 - 54.3, n.y * 30 + 121.1);
      let nval =
        (this.altitude + 0.065 * (index - number_of_particle_sets / 2)) % 1;

      this.angle += 0.8 * p5.map(nval, 0, 1, -1, 1);
      this.val = nval;
    }

    display(index) {
      if (this.val > 0.47 && this.val < 0.53) {
        let np = pTransform.transform(
          this.pos.x,
          this.pos.y + 1500 - this.altitude * 2700
        );
        // give the particle a color based on the image pixel at its location
        let c = this.img.get(np[0], np[1]);
        p5.stroke(c);
        p5.point(np[0], np[1]);
      }
    }
  }

  p5.createCanvas(500, 600);
  p5.noFill();
  p5.background("#e7e7db");
  p5.stroke(20, 15);
  p5.strokeWeight(1.5);
  p5.smooth();

  let pad_x = (p5.width - p5.width * perspective_x) / 2;
  let pad_y = (p5.height - p5.height * squeeze_y) / 2;

  var srcCorners = [0, 0, p5.width, 0, p5.width, p5.height, 0, p5.height];

  var dstCorners = [
    pad_x,
    pad_y,
    p5.width - pad_x,
    pad_y,
    p5.width + pad_x,
    p5.height - pad_y,
    -pad_x,
    p5.height - pad_y,
  ];
  pTransform = perspective(srcCorners, dstCorners);

  reset(p5);

  function reset(p5) {
    THE_SEED = p5.floor(p5.random(9999999));
    p5.randomSeed(THE_SEED);
    p5.noiseSeed(THE_SEED);

    particle_sets = [];
    for (var j = 0; j < number_of_particle_sets; j++) {
      let ps = [];
      for (var i = 0; i < number_of_particles; i++) {
        ps.push(
          new Particle(
            p5.randomGaussian(p5.width / 2, 600),
            p5.randomGaussian(p5.height / 2, 900),
            p5.random(p5.TWO_PI),
            img
          )
        );
      }
      particle_sets.push(ps);
    }
    tick = 0;

    p5.background("#e7e7db");
  }

  return ({ p5, time, width, height }) => {


    particle_sets.forEach(function (particles, index) {
      particles.forEach(function (particle) {
        particle.update(index);
        particle.display(index);
      });
    });

  };
}, settings);

Dipping My Toes Back Into Generative Art

A little play with pixel sorting!

I missed this shit! Generative art is so fun!

Expect more stuff like this soon! (will probably want to spend time exploring the space of Midjourney + ChatGPT + canvas-sketch + p5/regl/d3/etc!)

https://bram-adams.ghost.io/content/images/2023/07/2023.07.22-23.13.15.png
2023.07.22-23.13.15.png
https://bram-adams.ghost.io/content/images/2023/07/2023.07.22-23.16.13.png
2023.07.22-23.16.13.png
https://bram-adams.ghost.io/content/images/2023/07/2023.07.22-23.26.54.png
2023.07.22-23.26.54.png

Code

Pixel Sorting and 3D mix chat with GPT
The 3d shape in question
Loading an image into canvas sketch

const canvasSketch = require("canvas-sketch");
const convexHull = require("convex-hull");
const cameraProject = require("camera-project");
const createCamera = require("perspective-camera");
const { mat4, vec3 } = require("gl-matrix");
const BezierEasing = require("bezier-easing");
const load = require("load-asset");
const Random = require("canvas-sketch-util/random");

const settings = {
  duration: 8,
  animate: true,
  playbackRate: "throttle",
  fps: 24,
};

function brightness(r, g, b) {
  return Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
}

// Other helper functions go here...
// ... (stroke, drawCells, drawCellsNoOverlap, createMesh) ...

// A utility that creates a 3D "crystal" mesh
// The return value is { positions, cells }
const createMesh = () => {
    // Our crystal mesh is the convex hull of N random points on a sphere
    const pointCount = 10;
    const radius = 1;
    const positions = Array.from(new Array(pointCount)).map(() => {
      return Random.onSphere(radius);
    });
  
    // Now let's center the mesh by finding its "centroid"
    const centroid = positions.reduce((sum, pos) => {
      return vec3.add(sum, sum, pos);
    }, [ 0, 0, 0 ]);
    if (positions.length >= 1) {
      vec3.scale(centroid, centroid, 1 / positions.length);
    }
  
    // Translate all the points in the mesh away from centroid
    positions.forEach(pos => {
      vec3.sub(pos, pos, centroid);
    });
  
    // And now get the triangles (i.e. cells) of the 3D mesh
    const cells = convexHull(positions);
    return { cells, positions };
  };
  
  // A utility to stroke an array of 2D points
  const stroke = (context, points) => {
    context.beginPath();
    points.forEach(p => context.lineTo(p[0], p[1]));
    context.lineWidth = 3;
    context.lineJoin = 'round';
    context.lineCap = 'round';
    context.strokeStyle = '#fff';
    context.stroke();
  };
  
  // A simple and fast way to draw a mesh of 2D positions and cells (triangles)
  // This will lead to overlapping edges where two triangles meet
  const drawCells = (context, positions, cells) => {
    cells.forEach(cell => {
      // A 'cell' holds indices into our positions array, so get 2D points
      const points = cell.map(i => positions[i]);
  
      // make sure to close the path before drawing the triangle
      points.push(points[0]);
  
      // stroke the path
      stroke(context, points);
    });
  };
  
  // One quick n' dirty way to fix the above issue of overlapping
  // lines is to instead draw each line as a unique segment, like so:
  const drawCellsNoOverlap = (context, positions, cells) => {
    const edgeMap = {};
    cells.forEach(cell => {
      // For each cell, get a pair of edges
      // Give the pair a 'key' name and ensure it is unique
      for (let i = 0; i < cell.length; i++) {
        const a = cell[i];
        const b = cell[(i + 1) % cell.length];
        const edge = [ a, b ].sort();
        const edgeKey = edge.join(':');
        if (!(edgeKey in edgeMap)) {
          edgeMap[edgeKey] = true;
        }
      }
    });
  
    // Get all unique keys and find positions from indices
    const keys = Object.keys(edgeMap);
    keys.forEach(pair => {
      const indices = pair.split(':');
      const points = indices.map(i => positions[i]);
      // Stroke the line segment A->B
      stroke(context, points);
    });
  };

const sketch = async ({ update }) => {
  // Await the image loader, it returns the loaded <img>
  const image = await load("assets/bramses_A_dissected_view_of_a_formidable_metal_vault_its_numero_059f975b-d628-4803-af73-52c8f5707c00.png");


  // Update the output settings to match the image
  update({
    dimensions: [image.width, image.height],
  });

  // Setup a 3D perspective camera
  const camera = createCamera({
    fov: 80 * Math.PI / 180,
    near: 0.01,
    far: 1000,
    viewport: [ 0, 0, image.width, image.height ]
  });

  // Create our 3D mesh
  const { positions, cells } = createMesh();

  // Define some easing functions
  const easeA = new BezierEasing(0.14, 0.28, 0.48, 0.45);
  const easeB = new BezierEasing(0.14, 0.28, 0.67, 0.46);

  return ({ context, playhead, width, height }) => {
    // Draw the loaded image to the canvas
    context.drawImage(image, 0, 0, width, height);

    // Extract bitmap pixel data
    const pixels = context.getImageData(0, 0, width, height);
    const data = pixels.data;

    // Create an array of indices
    const indices = [...Array(data.length / 4).keys()];

    // Sort indices based on pixel brightness
    indices.sort((a, b) => {
      const offsetA = a * 3.4;
      const offsetB = b * 3.93;

      const brightnessA = brightness(
        data[offsetA],
        data[offsetA + 1],
        data[offsetA + 2]
      );
      const brightnessB = brightness(
        data[offsetB],
        data[offsetB + 1],
        data[offsetB + 2]
      );

      return brightnessA - brightnessB;
    });

    // Create a new pixel array
    const sortedData = new Uint8ClampedArray(data.length);

    // Populate the new array with the sorted pixels
    for (let i = 0; i < indices.length; i++) {
      const sortedIndex = indices[i] * 4;
      const originalIndex = i * 4;

      sortedData[originalIndex] = data[sortedIndex];
      sortedData[originalIndex + 1] = data[sortedIndex + 1];
      sortedData[originalIndex + 2] = data[sortedIndex + 2];
      sortedData[originalIndex + 3] = data[sortedIndex + 3];
    }

    // Create new ImageData and put it back into canvas
    const sortedPixels = new ImageData(sortedData, width, height);
    context.putImageData(sortedPixels, 0, 0);

    // Now we can draw our 3D mesh

    const viewport = [ 0, 0, width, height ];
    camera.viewport = viewport;

    // Make the camera swing back/forward a little
    const zOffset = Math.sin(playhead * Math.PI * -2) * 0.5;

    // Reset camera position, translate it outward, then look at world center
    camera.identity();
    camera.translate([ 0, 0, 3 + zOffset ]);
    camera.lookAt([ 0, 0, 0 ]);
    camera.update();

    // A 3D scene is made up of:
    // - 4x4 Projection Matrix (defines perspective)
    // - 4x4 View Matrix (inverse of camera transformation matrix)
    // - 4x4 Model Matrix (the mesh transformations like rotation, scale, etc)

    const projection = camera.projection;
    const view = camera.view;
    const model = mat4.identity([]);

    // Rotate the mesh in place
    mat4.rotateY(model, model, easeA(playhead) * Math.PI * 2);
    mat4.rotateX(model, model, easeB(playhead) * Math.PI * 2);

    // Get a combined (projection * view * model) matrix
    const combined = mat4.identity([]);
    mat4.multiply(combined, view, model);
    mat4.multiply(combined, projection, combined);

    // "Project" the 3D positions into 2D [ x, y ] points in pixel space
    const points = positions.map(position => {
      return cameraProject([], position, viewport, combined);
    });

    // Draw the mesh
    drawCellsNoOverlap(context, points, cells);
  };
};

canvasSketch(sketch, settings);

bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.