CATS+ Dance: Logic Machine Coming On- and Offline with Virus

From MoHA Wiki


By Itai Almor CATS+ Out of the BagPublished: February 1, 2024

This is an artist page by Itai Almor. This project was part of the 2023 CATS+ Computer Club cohort.

Investigation

To transpose the cold and precise logic of computer code onto the imperfect, inconsistent, emotional mind-muscle machine of the human body. To deepen understanding of the human-computer dichotomy. To attempt embodied knowledge of the machine. To parse the desire for more intimate co-relational interactions with computer technologies.

Phase 1: The Workshop

Misc Goals

  • To have fun together
  • To build community
  • To lead a dance workshop
  • To get in our bodies (for a change)
  • To make a dance video

Brainstorm/Notes/Plan (transcribed)

  • Warm up (5 min)
    • Couple big stretches
    • tip toes
    • now walk + eye contact
    • levels
    • backwards
    • change speed
    • do a spin as you go
    • secretly follow someone
  • Gaga (10 min)
    • Find a spot to experiment with different types of motion
    • Never stop moving
    • Play with: circles, angles, small, face, connecting diagonal points
    • Shake it out
  • Show diagrams/images of computer architecture (10 min)
    • “Room read” based off of them
  • Pick 3-4 movements you can repeat on loop (5 min)
  • Show the group (5 min)
  • Conversation: Loop conditions & End Conditions & iterate conditions (15 min)
    • Iterations
      • Change level
      • Rotate 90 deg
      • Get bigger
      • Get smaller
      • Get faster
      • Get slower
      • Take i++ steps and redo
      • If there's a clap, I++ the motion before it
      • If the time rings, all stop
    • End/restart conditions
      • Change level
      • Rotate 90 deg
      • Get bigger
      • Get smaller
      • Get faster
      • Get slower
      • Take i++ steps and redo
      • If there's a clap, I++ the motion before it
  • Run Program and film (40 min)
Photo of Itai's handwritten notes

Comments

The workshop was a success. Everybody enjoyed the very different type of collaboration. Everyone was invited to make marks on their skin with face paint for extra, playful visual interest. J moved among the dancers filming with an iPhone mounted on a gimbal. My camera was set on a tripod on the raised stage, filming a constant overview angle. We ran through our choreography three times fully, setting the timer for three minutes each.

I edited the footage into a 9 minute loop, showing the “logic machine” starting and stopping and responding to the “virus” 3 times through. I desaturated the footage to black and white, heightening the contrast and leaning into the graininess of extreme digital zooms to center the machine aesthetics we mimicked with our bodies.

Still from dance video. Figures in various static, geometric poses
Dance video still

Phase 2: Code

In discussion after the workshop we arrived at the idea of writing actual code in p5.js to describe the movement logics we had created. This code would be visualized in a browser using icons as stand-ins for our bodies. For this purpose, we documented our choreographies verbally on a Notion doc, each of us using whatever language and descriptions made sense to them:

Choreo copies from the Notion

3/4 Dance Choreographer Logics

Itai

  • Movement inspired by linear moving pieces like machinework, combined with a big round screenwipe
  • Do my routine with increasing speed each time.
  • After every iteration turn 90 degrees to the right and then clap
  • Stop when Jesse taps me and when Jesse is motionless/disappears
  • Restart after 5 seconds stopped

Kristine
Mix of code and pseudo code

   const armSegments = 5;
   const totalArmAngle = radians(90); // convert degrees to radians
   const armAngle = totalArmAngle / armSegments;
   let runLoop = true;
   let rotateIndex = 0;
   const bodyAngle = radians(60);
   const kristine = new Kristine();

   kristine.body.rotate(radians(-70));

   while (runLoop){
     for (let i = 0; i < segments; i++){   
        // arms are pointed to front for the following line
        kristine.arms.to(totalAngle - armAngle * i);                      
        kristine.arms.pseudo('raise arms up touch hands at top, look up')
        kristine.arms.pseudo('arms lower to point in front')
     }
     rotateIndex++;
     kristine.body.pseudo(`spread legs out 1.5 times the width of shoulders at ${bodyAngle * (rotateIndex is even ? 1 : -1)} relative   to current position`)
     kristine.body.pseudo('squat for 1 second');
     kristine.body.pseudo('stand up with legs still spread apart');
     kristine.body.pseudo('legs back together, feet side by side');
   }

   kristine.on('touch', () = {
      runLoop = false;
      setTimeout(() => {
          runLoop = true
      }, 10000); // 10 second timeout on touch
   });

   kristine.on('timer', () => {
     runLoop = false;
   });


Rachel
Movement A is inspired by surfing and clicking. I swoop forward in a casual swaying step, accentuated by a curving arm motion with each step for 3 steps (left, right, left) then jump to signify a click. When I reach the end of the field, I curve around to go the other direction (inspired by the classic mobile game, Snake). Movement B is inspired by binary code. I stand upright with arms touching overhead to form a 1, then swing down into a squat with my arms forming a 0, repeating and moving forward in a zigzag motion across the length of the space. Program loop: Run A for 3 cycles; then Run B until at end of field; then Pivot 180 degrees and repeat. If Jesse touches me, then timeout for 8 seconds; then restart program.

Sara
My dance was like a motor or machine, jumping back and forth and switching the angle of my arms, I would do this a random amount of times >6 and then rotate. If Itai clapped I would change my dance to stepping horizontally and maximizing a window, stepping and maximizing a window, then spinning 180 and going back the direction I came. If Jesse-virus infected me I would slow down and “shut down”. Sometimes the program ran into difficulty facing the choice between responding to noise stimulus and virus infiltration. Resulting in switching dances instead of slowing down.

Sam

  • The theater chair: Feet hip-width distance apart, palms out in front of you parallel to the floor. Squat. Bring your palms down to touch the floor, together. x3
  • The plug: Feet hip-width distance apart, lean back, feet firmly planted. Two hands out in front of you, fingers bunched to make three pokies. Thrust them forward 3 times. Straighten back up. x3
  • The response: Feet hip-width distance apart, lean back, feet firmly planted. Circle hips twice one direction. Twice opposite direction. Straighten back up. x3

Serena

  • Main/ default dance: Moved only along the “hinges” of my body (wrists, elbows, waist, etc) to imitate the look of programmatic choreography of robotics, ended up looking kinda wavy
  • When someone crosses the line in the floor, turn 180 degrees in one hop and continue with the wavy dance
  • Speed is based on how many of Jessie’s limbs were on the floor
    • if Jessie is laying flat on the floor then I’m stopped
    • if Jessie is standing, then I am going the normal speed
    • If 1/4 limbs are on the ground → I’m going .75 speed, if 1/2 limbs on the ground → .5 speed, etc

p5.js code, written by Kellyn

   let img1, img2, img3; let imgWidth = 100; let imgHeight = 100;
   //(move the image down until it hits the bottom of the canvas at a slow speed. when it hits the bottom of the canvas, move  the image up until it returns to the starting point.) do this three times. (then rotate the image 15 degrees. move y position of   the image up by 10 pixels. then rotate the image in the opposite direction 15 degrees.) do this three times. then rotate the   image 360 degrees. then rotate it -780 degrees. do this three times.
   function preload() {
   img1 = loadImage('./assets/CatsDanceAvatar_Itai.png');
   img2 = loadImage('./assets/jesse_virus.webp');
   img3 = loadImage('./assets/rachel_snake.png');
   img4 = loadImage('./assets/sara_gears.png');
   img5 = loadImage('./assets/serena_robot2.png');
   img6 = loadImage('./assets/sam_plug.png');
   img7 = loadImage('./assets/kellyn_spinning_wheel_of_death.jpeg');
   }
   class ImageObject {
   constructor(img, x, y, speed, rotationSpeed) {
   this.img = img;
   this.x = x;
   this.y = y;
   this.speed = speed; this.rotationSpeed = rotationSpeed; this.angle = 0;
   this.movementCount = 0; this.movementDirection = 1; this.speedMultiplier = 1;
   }
   update() {
   // move the image up and down this.y += this.speed;
   if (this.y > height || this.y < 0) {
   this.speed *= -1; }
   // rotate the image in place this.angle += this.rotationSpeed; if (this.angle >= 360) {
   this.angle = 0; }
   // move the image left and right if (this === image4) {
   if (this.movementCount >= 6) {
   // rotate the image by 90 degrees after 6 left-right movements this.angle += 90;
   this.movementCount = 0;
   this.speedMultiplier = 1;
   } else {
   this.x += this.speed * this.movementDirection * this.speedMultiplier; if (this.x <= 0 || this.x >= width) {
   this.movementCount++; this.movementDirection *= -1; this.speedMultiplier += 0.2;
   } }
   }
   // move the image down if (this === image6) {
   if (this.movementCount <= 3) { this.y += this.speed;
   if (this.y > height || this.y < 0) {
   this.speed *= -1; }
   this.angle += 360;
   this.movementCount = 0; } else {
   if (this.x <= 0 || this.x >= width) { this.angle -= 780; this.movementCount++;
   } }
   } }
   display() { push();
   translate(this.x, this.y); rotate(radians(this.angle)); imageMode(CENTER); image(this.img, 0, 0); pop();
   } checkCollision(other) {
   let distance = dist(this.x, this.y, other.x, other.y);
   return distance <= (this.image.width * this.size + other.image.width * other.size) / 2; }
   jitter() {
   // zigzag movement for image3 if (this === image3) {
   if (this.x <= 0 || this.x >= width) { this.speed *= -1;
   }
   this.x += this.speed; this.y += this.speed * 0.5;
   } else { // random jitter movement for image1 and image2 this.x += random(-5, 5);
   this.y += random(-5, 5);
   if (this.x < 0) {
   this.x = width;
   } else if (this.x > width) {
   this.x = 0; }
   if (this.y < 0) { this.y = height;
   } else if (this.y > height) { this.y = 0;
   } }
   // reduce the movement speed of image4 when colliding with image2
   if (this === image4 && dist(this.x, this.y, image2.x, image2.y) < imgWidth) {
   this.speed = 0;
   this.speedMultiplier = 0; }
   }
   reset() {
   this.x = random(width);
   this.y = random(height);
   this.speed = random(1, 5); this.rotationSpeed = random(0.5, 1); this.angle = 0;
   this.movementCount = 0; this.movementDirection = 1; this.speedMultiplier = 1;
   } }
   class Line {
   constructor() {
   this.x1 = random(width);
   this.y1 = random(height);
   this.x2 = random(width);
   this.y2 = random(height);
   this.color = color(random(255), random(255), random(255)); this.thickness = random(1, 5);
   }
   draw() {
   stroke(this.color); strokeWeight(this.thickness); line(this.x1, this.y1, this.x2, this.y2);
   } }
   class WavyMovingImage { constructor(img) {
   this.img = img;
   this.x = random(width); this.y = random(height); this.speed = 1; this.angle = 0; this.amplitude = 50; this.frequency = 0.05;
   }
   move() {
   this.x += this.speed;
   this.angle += this.frequency;
   this.y = map(sin(this.angle), -1, 1, this.y - this.amplitude, this.y + this.amplitude);
   if (this.x > width) { this.x = 0;
   } }
   display() { push();
   translate(this.x, this.y); rotate(this.angle); imageMode(CENTER); image(this.img, 0, 0); pop();
   }
   collidesWithLine(line) {
   let d = dist(this.x, this.y, line.x1, line.y1) + dist(this.x, this.y, line.x2, line.y2); let lineLength = dist(line.x1,                      line.y1, line.x2, line.y2);
   let tolerance = 5;
   return (d >= lineLength - tolerance && d <= lineLength + tolerance);
   }
   turn(degrees) {
   this.angle += radians(degrees);
   } }
   let image1, image2, image3, image4, image5, image6, image7; let lines = [];
   function setup() { createCanvas(1100, 700);
   for (let i = 0; i < 20; i++) { lines.push(new Line());
   }
   //play with this to size or resize img1.resize(imgWidth, imgHeight); img2.resize(imgWidth, imgHeight); img3.resize(imgWidth,   imgHeight); img4.resize(imgWidth, imgHeight); img5.resize(imgWidth, imgHeight); img6.resize(imgWidth, imgHeight);    img7.resize(imgWidth, imgHeight);
   image1 = new ImageObject(img1, random(width), random(height), random(1, 5), random(0.5, 1));
   image2 = new ImageObject(img2, random(width), random(height), random(1, 5), random(0.5, 1));
   image3 = new ImageObject(img3, random(width), random(height), random(1,
   5), 0); image4 = new ImageObject(img4, random(width), random(height), random(1, 5),
   random(0.5, 1));
   image5 = new WavyMovingImage(img5);
   image6 = new ImageObject(img6, random(width), random(height), random(1, 5),
   random(0.5, 1));
   image7 = new ImageObject(img7, random(width), random(height), random(1, 5),
   random(0.5, 1)) }
   function draw() { background(220);
   // Draw lines
   for (let i = 0; i < lines.length; i++) {
   lines[i].draw(); }
   image1.jitter(); image1.update(); image1.display();
   image2.jitter(); image2.update(); image2.display();
   image3.jitter(); image3.update(); image3.display();
   image4.jitter(); image4.update(); image4.display();
   image5.move(); image5.display();
   image6.update(); image6.display(); image6.jitter();
   image7.update(); image7.display(); image7.jitter();
   // reset image1 and image2 if they go offscreen
   if (image1.x > width || image1.x < 0 || image1.y > height || image1.y < 0) {
   image1.reset(); }
   if (image2.x > width || image2.x < 0 || image2.y > height || image2.y < 0) { image2.reset();
   }
   // reset the third image if it goes off screen
   if (image3.x < -imgWidth / 2 || image3.x > width + imgWidth / 2 || image3.y <
   -imgHeight / 2 || image3.y > height + imgHeight / 2) { image3.reset();
   }
   // check for collisions with lines for (let i = 0; i < lines.length; i++) {
   if (image5.collidesWithLine(lines[i])) { image5.turn(180);
   break;
   } }
   }
   function mouseClicked() { image1.reset(); image2.reset(); image3.reset(); image4.reset(); image5.reset(); image6.reset();  

The icon I made to stand in for myself

Illustrated and collage-like figure with fins, scales, orb like head
I think it matches my vibe.

We ran the code at two scale factors and screen-captured the graphics to create the material for our second video. I edited it into a 2 minute loop.

Image of code visualization
Code visualization still


Phase 3: AI Generated Video

We had already visualized an attempt to map machine logic onto human bodies and created a pure code rendering of the same motions. This felt like we were gesturing to a gradient: human - code - … and the third element would be an even more perfect abstraction away from the personal, toward a pure computer isolate. To create a fitting piece, I settled on using excerpts from our verbal choreographic descriptions as input to generate video using a multi-stage text-to-video generation diffusion model called Modelscope (https://huggingface.co/damo-vilab/modelscope-damo-text-to-video-synthesis)

Examples of phrases lifted to create video include:

  • “A casual swaying step”
  • “Arms lower to point in front”
  • “Circle hips twice”
  • “Jessie is laying flat on the floor”
  • “My dance was like a motor or a machine”
  • “Squat for one second”
  • “The wavy dance”

The tool generated two-second clips based off of each phrase. These I edited together to create our third video, a one-minute loop.

Image of two feet from AI generated video
AI generated video still 1
Image of grid of lines/graphics from AI generated video
AI generated video still 2

Final Showcase: Sculpture

Our intention was to screen two feeds of each video on a sculptural arrangement of six CRT TV’s. In the final moments before our showcase, our signal flow broke, and we were only able to play five screens of the dancing footage loop and one of the AI video loop. Alongside these we displayed a printed-out booklet of the choreo descriptions from the Notion and another of the code rendition of those logics.

Installation view of stacked monitors with figures watching
Installation view 1
Installation view detail of monitors stacked on cinderblocks
Installation view 2


Related links