Week 11: In-class Activity (by Taskin & Zavier)

# Jump To:


# Introduction

Hi there! 👋

These are a few in-class activities Taskin and I did this week (and had to post), resolving around serial communication between p5 and Arduino.

# Exercise 1: Arduino Affecting p5

 

Task:

“Make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on arduino is controlled by p5.”

 

Code:
Arduino:
 
void setup() {
	Serial.begin(9600);
	pinMode(A0, INPUT);
}

void loop() {
	Serial.println(analogRead(A0));
	delay(10);
}
 
p5:
 
let xPos = 0;

function setup() {
	createCanvas(600, 600);
	noFill();
}

function draw() {
	background(32, 64);
	
	stroke("white")

	ellipse(map(xPos, 0, 1023, 0, width), height/2, 100, 100);
	
	// Turn the screen red to make it very clear that we aren't connected to the Arduino
	if (!serialActive)
		background(128, 0, 0);
}

function keyPressed() {
	if (key == " ")
		setUpSerial(); // Start the serial connection
}

function readSerial(data) {
	if (data != null) // Ensure there's actually data
		xPos = int(data);
}			
 

 

# Exercise 2: p5 Affecting Arduino

 

Task:

“Make something that controls the LED brightness from p5.”

 

Code:
Arduino:
 
void setup() {
	Serial.begin(9600);
	pinMode(3, OUTPUT);
}

void loop() {
	analogWrite(3, Serial.parseInt());
	Serial.println();
	delay(10);
}
 
p5:
 
let xPos = 0;
let LEDBrightness = 0; // 0 - 255

function setup() {
	createCanvas(600, 600);
}

function draw() {
	if (keyIsDown(UP_ARROW) && LEDBrightness < 255) LEDBrightness += 1; else if (keyIsDown(DOWN_ARROW) && LEDBrightness > 0)
		LEDBrightness -= 1;
	
	// Just a visual indicator of the brightness level on p5
	background(LEDBrightness);
	fill(LEDBrightness < 128 ? 'white' : 'black')
	text(LEDBrightness, 25, 25);
	
	// Turn the screen red to make it very clear that we aren't connected to the Arduino
	if (!serialActive)
		background(128, 0, 0);
}

function keyPressed() {
	if (key == " ")
		setUpSerial(); // Start the serial connection
}

function readSerial(data) {
	writeSerial(LEDBrightness);
}
 

 

# Exercise 3: Arduino and p5 Affecting Each Other

 

Task:

“Take the gravity wind example and make it so every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor.”

 

Code:
Arduino:
 
const int LED_PIN = 3;
const int POT_PIN = A0;

void setup() {
	Serial.begin(9600);
	pinMode(LED_PIN, OUTPUT);
	pinMode(POT_PIN, INPUT);

	// Start the handshake
	while (Serial.available() <= 0) {
		Serial.println("0"); // Send a starting message
		delay(300); // Wait ~1/3 second
	}
}

void loop() {
	while (Serial.available()) {
		int LEDState = Serial.parseInt(); // 0 or 1
		
		if (Serial.read() == '\n') {
			digitalWrite(LED_PIN, LEDState);
			Serial.println(analogRead(POT_PIN));
		}
	}
}
 
p5:
 
let position, velocity, acceleration, gravity, wind; // vectors
let drag = 0.99, mass = 50, hasBounced = false;

function setup() {
	createCanvas(640, 360);
	noFill();
	position = createVector(width/2, 0);
	velocity = createVector(0,0);
	acceleration = createVector(0,0);
	gravity = createVector(0, 0.5*mass);
	wind = createVector(0,0);
	
	textAlign(CENTER);
	textSize(18);
}

function draw() {
	background(255);
	applyForce(wind);
	applyForce(gravity);
	velocity.add(acceleration);
	velocity.mult(drag);
	position.add(velocity);
	acceleration.mult(0);
	fill(hasBounced ? 'green' : 'white')
	ellipse(position.x,position.y,mass,mass);
	if (position.y > height-mass/2) {
		velocity.y *= -0.9;  // A little dampening when hitting the bottom
		position.y = height-mass/2;
		
		if (!hasBounced && abs(velocity.y) > 1) {
			hasBounced = true;
			setTimeout(() => hasBounced = false, 100); // Set hasBounced to false after 0.1 s
		}
	}  
	
	if (!serialActive) {
		background(8);
		fill('white');
		text("Press c to connect to the Arduino", width/2, height/2)
	}
}

function applyForce(force){
	// Newton's 2nd law: F = M * A
	// or A = F / M
	let f = p5.Vector.div(force, mass);
	acceleration.add(f);
}

function keyPressed(){
	if (key == ' '){
		mass = random(15,80);
		position.set(width/2, -mass);
		velocity.mult(0);
		gravity.y = 0.5*mass;
		wind.mult(0);
		
	} else if (key == 'c') {
		setUpSerial(); // Start the serial connection
	}
}

function readSerial(data) {
	if (data != null) { // Ensure there's actually data
		wind.x = map(int(data), 0, 1023, -2, 2);
		writeSerial((hasBounced ? 1 : 0) + '\n');
	}
}
 

 

Circuit:

 

Demo:

Week 10: Musical Instrument – Handisyn (Group project by Zavier & Xiaotian)

INTRO

What makes something an instrument? I’d like to borrow a concept from Rehding 2016, Three Music-Theory Lessons: ‘epistemic things,’ things that we use to practice knowledge while themselves are the knowledge in terms of how knowledge is generated. Aside from the extent to which a person can easily make acoustically pleasant sound waves out of an installation, what constitutes an instrument is that itself is an epistemic thing—having connections with and making it feasible to practice our common musical knowledge on while embodying a distinct system that generates/contributes to musical knowledge/practice.

That being said, how we divided our work is basically mapping the two sides (and the two stage of the development) to the two of us—the music and the interface, the backend and the frontend, the soft and the hard, etc. On my side, I had the Arduino and the speaker to start up a very basic synthesizer consisting of two audio oscillators and two control oscillators.

Process

In a nutshell, what I tried to realize is a simple cosine wave oscillator frequency modulated according to the four input parameters that control root frequency: intensity (the extent to which the root signal is modulated to the target frequency), vibrato speed (the rate at which the modulation is automatically carried on), and modulation ratio (the multiple to determine the target frequency from the root).

#include <Mozzi.h>
#include <Oscil.h> // oscillator
#include <tables/cos2048_int8.h> // table for Oscils to play
#include <Smooth.h>
#include <AutoMap.h> 

// desired carrier frequency max and min, for AutoMap
const int MIN_CARRIER_FREQ = 22;
const int MAX_CARRIER_FREQ = 440;

// desired intensity max and min, for AutoMap, inverted for reverse dynamics
const int MIN_INTENSITY = 10;
const int MAX_INTENSITY = 1000;

// desired modulation ratio max and min
const int MIN_MODRATIO = 5;
const int MAX_MODRATIO = 2;

// desired mod speed max and min, for AutoMap, note they're inverted for reverse dynamics
const int MIN_MOD_SPEED = 10000;
const int MAX_MOD_SPEED = 1;

AutoMap kMapCarrierFreq(400,700,MIN_CARRIER_FREQ,MAX_CARRIER_FREQ);
AutoMap kMapIntensity(400,700,MIN_INTENSITY,MAX_INTENSITY);
AutoMap kMapModRatio(400,700,MIN_MODRATIO,MAX_MODRATIO);
AutoMap kMapModSpeed(400,700,MIN_MOD_SPEED,MAX_MOD_SPEED);

const int FREQ_PIN = 0; 
const int MOD_PIN = 1; 
const int RATIO_PIN = 3;
const int SPEED_PIN = 2;

Oscil<COS2048_NUM_CELLS, MOZZI_AUDIO_RATE> aCarrier(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, MOZZI_CONTROL_RATE> kIntensityMod(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, MOZZI_AUDIO_RATE> aModulator(COS2048_DATA);

int mod_ratio; // harmonics
long fm_intensity; // carries control info from updateControl() to updateAudio()

// smoothing for intensity to remove clicks on transitions
float smoothness = 0.95f;
Smooth <long> aSmoothIntensity(smoothness);

void setup(){
  Serial.begin(115200); // set up the Serial output for debugging
  startMozzi(); 
}

void updateControl(){
  // read the freq
  int freq_value = mozziAnalogRead<10>(FREQ_PIN); // value is 0-1023

  // map the input to carrier frequency
  int carrier_freq = kMapCarrierFreq(freq_value);

  // read the ratio
  int ratio_value = mozziAnalogRead<10>(RATIO_PIN);

  // map the input to ratio
  mod_ratio = kMapModRatio(ratio_value);

  //calculate the modulation frequency to stay in ratio
  int mod_freq = carrier_freq * mod_ratio;
  
  // set the FM oscillator frequencies to the calculated values
  aCarrier.setFreq(carrier_freq);
  aModulator.setFreq(mod_freq);

  // calculate the fm_intensity
  int mod_level= mozziAnalogRead<10>(MOD_PIN); // value is 0-1023
  fm_intensity = ((long)mod_level * (kIntensityMod.next()+128))>>8;

  // use a float here for low frequencies
  int speed_value = mozziAnalogRead<10>(SPEED_PIN);
  float mod_speed = (float)kMapModSpeed(speed_value)/1000;
  kIntensityMod.setFreq(mod_speed);
}

AudioOutput updateAudio(){
  long modulation = aSmoothIntensity.next(fm_intensity) * aModulator.next();
  return MonoOutput::from8Bit(aCarrier.phMod(modulation)); // phMod does the FM
}

void loop(){
  audioHook();
}

THE GLOVE

Zavier here, now it’s my turn! So after testing the circuit and code (huge thanks to Xiaotian for the awesome sound control!), I began working on attaching it to the glove, which was… painful. I first just measured out and positioned things, then I started attaching them with my small transparent tape (which isn’t the ideal way (sowing it would be better), and besides my tape wasn’t strong at all). After asking a friend for help, I got the sensors attached. Ok, the easy part done. Now the troubles begin. You see, I obviously had to connect the flex sensors to the Arduino. I thought I could I just use the female-male wires, but nope! The pins on the flex sensors were too small, so it was far too lose :(. I tried thinking of a few other options, but in the end, I had to do what I was trying to avoid, soldering. To be honest, I didn’t even mind it that much before (past me talking), and thought it would take a few fun minutes, but boy oh boy. I don’t know what it is about these solders (maybe they’re dirty?), but only a very tiny bit of the tip is actually going to melt the solder, so a lot of time was spent just moving and rotating the tip. Also, I shouldn’t have attached the flex sensors already. It was a huge pain to get them soldered. Now admittedly, (probably a large) part of that is because I’ve hardly soldered before, but also (in addition to the tip issue), I was making these 3 point connections (such as connecting the ground of one the flex sensors, to the ground of the adjacent ones), so whenever I tried soldering, it would just release the other 2!

Anyways, after some work, I finally got the flex sensors wired up, and it was finally working. Great! Ok, we’re done… we’re done?…. right?. Haha, nope. I thought it would be a good idea to add neopixels (addressable LED strips) to the gloves too. After testing a strip, I didn’t repeat my mistake, and this time I soldered together the strips first, before attaching them. This went a lot smoother (also thanks to just having some experience doing it for the flex sensors), but it still took sooo long. Unfortunately, since I soldered it first, the connections weren’t the right length 🙂. Luckily, I had expected them not to be precisely correct (and besides the distance between the strips would change a bit as the hand was flexed and relaxed), and so kept it a bit longer, so that the length could be adjusted as needed. This unintentionally also ended up creating a nice pattern 😅.

While it was a lot of work, it definitely made things a LOT cooler, and also provided a way to give some info to the user visually.

 

Final result:

 

 

 

Demo (note: the sound is very distorted in the recording)

 

Code:

// Configuring Mozzi's options
#include <MozziConfigValues.h>
#define MOZZI_ANALOG_READ_RESOLUTION 10 // Not strictly necessary, as Mozzi will automatically use the default resolution of the hardware (eg. 10 for the Arduino Uno), but they recommend setting it (either here globally, or on each call)

#include <Mozzi.h>
#include <Oscil.h> // oscillator
#include <tables/cos2048_int8.h> // table for Oscils to play
#include <Smooth.h>
#include <AutoMap.h>

#include <FastLED.h>


// Flex sensor stuff

// Define flex sensor pins (these have to be analog)
const int FREQ_SENSOR_PIN = A0;
const int MOD_SENSOR_PIN = A1;
const int SPEED_SENSOR_PIN = A2;
const int RATIO_SENSOR_PIN = A3;

// Smoothening for each pin (was previously using rolling averages)
Smooth<unsigned int> smoothFreq(0.8f);
Smooth<unsigned int> smoothMod(0.5f);
Smooth<unsigned int> smoothSpeed(0.75f);
Smooth<unsigned int> smoothRatio(0.5f);

// Input ranges for flex sensors (will be calibrated)
unsigned int freqInputMin = 1000; // Just FYI, the flex sensors in our setup roughly output in the range of ~ 200 - 650
unsigned int freqInputMax = 0;
unsigned int modInputMin = 1000;
unsigned int modInputMax = 0;
unsigned int speedInputMin = 1000;
unsigned int speedInputMax = 0;
unsigned int ratioInputMin = 1000;
unsigned int ratioInputMax = 0;


// Neopixel (addressable LED strip) stuff

// Define neopixel pins
const int FREQ_NEOPIXEL_PIN = 2;
const int MOD_NEOPIXEL_PIN = 3;
const int SPEED_NEOPIXEL_PIN = 4;
const int RATIO_NEOPIXEL_PIN = 5;

// Number of LEDs in each strip
const int NEOPIXEL_NUM_LEDS = 11;

// Define the array of leds
CRGB freqLEDs[NEOPIXEL_NUM_LEDS];
CRGB modLEDs[NEOPIXEL_NUM_LEDS];
CRGB speedLEDs[NEOPIXEL_NUM_LEDS];
CRGB ratioLEDs[NEOPIXEL_NUM_LEDS];


// Sound stuff

// desired carrier frequency max and min, for AutoMap
const int MIN_CARRIER_FREQ = 22;
const int MAX_CARRIER_FREQ = 440;

// desired intensity max and min, for AutoMap, inverted for reverse dynamics
const int MIN_INTENSITY = 10;
const int MAX_INTENSITY = 1000;

// desired modulation ratio max and min
const int MIN_MOD_RATIO = 5;
const int MAX_MOD_RATIO = 2;

// desired mod speed max and min, for AutoMap, note they're inverted for reverse dynamics
const int MIN_MOD_SPEED = 10000;
const int MAX_MOD_SPEED = 1;

Oscil<COS2048_NUM_CELLS, MOZZI_AUDIO_RATE> aCarrier(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, MOZZI_CONTROL_RATE> kIntensityMod(COS2048_DATA);
Oscil<COS2048_NUM_CELLS, MOZZI_AUDIO_RATE> aModulator(COS2048_DATA);

int mod_ratio; // harmonics
long fm_intensity; // carries control info from updateControl() to updateAudio()

// smoothing for intensity to remove clicks on transitions
float smoothness = 0.95f;
Smooth<long> aSmoothIntensity(smoothness);


void setup(){
  Serial.begin(9600); // set up the Serial output for debugging

  // Set the flex sensor pins
  pinMode( FREQ_SENSOR_PIN, INPUT_PULLUP);
  pinMode(  MOD_SENSOR_PIN, INPUT_PULLUP);
  pinMode(SPEED_SENSOR_PIN, INPUT_PULLUP);
  pinMode(RATIO_SENSOR_PIN, INPUT_PULLUP);

  // Setup the neopixels
    FastLED.addLeds<NEOPIXEL, FREQ_NEOPIXEL_PIN>(freqLEDs, NEOPIXEL_NUM_LEDS);
    FastLED.addLeds<NEOPIXEL, MOD_NEOPIXEL_PIN>(modLEDs, NEOPIXEL_NUM_LEDS);
    FastLED.addLeds<NEOPIXEL, SPEED_NEOPIXEL_PIN>(speedLEDs, NEOPIXEL_NUM_LEDS);
    FastLED.addLeds<NEOPIXEL, RATIO_NEOPIXEL_PIN>(ratioLEDs, NEOPIXEL_NUM_LEDS);
    FastLED.setBrightness(32); // 0 - 255

  // Feed/prime/initialise the smoothing function to get a stable output from the first read (to ensure the calibration isn't messed up). A value of 1630 was chosen by trial and error (divide and conquer), and seems to work best (at least for our setup)
  smoothFreq.next(1630);
  smoothMod.next(1630);
  smoothSpeed.next(1630);
  smoothRatio.next(1630);

  startMozzi(); 
}


// Basically our actual traditional loop in Mozzi (but still needs to kept reasonably lean and fast)
void updateControl(){

  // Read the smoothened freq
  int freqValue = smoothFreq.next(mozziAnalogRead(FREQ_SENSOR_PIN - 14)); // value is 0-1023, -14 since mozzi just takes a number (eg. 0 instead of A0), and the analog ones are 14 onwards

  // Calibrate the mapping if needed
  if (freqValue < freqInputMin) freqInputMin = freqValue;
  if (freqValue > freqInputMax) freqInputMax = freqValue;

  // Map the input to the carrier frequency
  int carrier_freq = map(freqValue, freqInputMin, freqInputMax, MIN_CARRIER_FREQ, MAX_CARRIER_FREQ);


  // Read the smoothened ratio
  int ratioValue = smoothRatio.next(mozziAnalogRead(RATIO_SENSOR_PIN - 14));

  // Calibrate the mapping if needed
  if (ratioValue < ratioInputMin) ratioInputMin = ratioValue;
  if (ratioValue > ratioInputMax) ratioInputMax = ratioValue;

  // Map the input to the ratio
  mod_ratio = map(ratioValue, ratioInputMin, ratioInputMax, MIN_MOD_RATIO, MAX_MOD_RATIO);


  // calculate the modulation frequency to stay in ratio
  int mod_freq = carrier_freq * mod_ratio;
  
  // set the FM oscillator frequencies to the calculated values
  aCarrier.setFreq(carrier_freq);
  aModulator.setFreq(mod_freq);


  // Read the smoothened mod
  int modValue = smoothMod.next(mozziAnalogRead(MOD_SENSOR_PIN - 14));

  // Calibrate the mapping if needed
  if (modValue < modInputMin) modInputMin = modValue;
  if (modValue > modInputMax) modInputMax = modValue;

  // Calculate the fm_intensity
  fm_intensity = ((long)modValue * (kIntensityMod.next()+128))>>8;


  // Read the smoothened speed
  int speedValue = smoothSpeed.next(mozziAnalogRead(SPEED_SENSOR_PIN - 14));

  // Calibrate the mapping if needed
  if (speedValue < speedInputMin) speedInputMin = speedValue;
  if (speedValue > speedInputMax) speedInputMax = speedValue;

  // use a float here for low frequencies
  float mod_speed = (float)map(speedValue, speedInputMin, speedInputMax, MIN_MOD_SPEED, MAX_MOD_SPEED) / 1000;
  kIntensityMod.setFreq(mod_speed);


  // Set the leds

  FastLED.clear(); // Resets them

  // The frequency controls how many of the LEDs are light up (in a rainbow colour)
  int freqLEDAmount = map(freqValue, freqInputMin, freqInputMax, 0, NEOPIXEL_NUM_LEDS);
  fill_rainbow(&freqLEDs[NEOPIXEL_NUM_LEDS - freqLEDAmount], freqLEDAmount, CRGB::White, 25); // &...LEDs[i] to start lighting from there, allowing us to light them in reverse

  // For the mod, show a meter (blue - deep pink) showing the mix level of the 2 sounds
  int modLEDAmount = map(modValue, modInputMin, modInputMax, 0, NEOPIXEL_NUM_LEDS);
  fill_solid(modLEDs, NEOPIXEL_NUM_LEDS, CRGB::Blue);
  fill_solid(&modLEDs[NEOPIXEL_NUM_LEDS - modLEDAmount], modLEDAmount, CRGB::DeepPink);

  // The speed controls the blinking rate of its LEDs (between 1/2 to 3 seconds per blink cycle)
  int speedLEDBlinkRate = map(speedValue, speedInputMin, speedInputMax, 500, 3000);
  if (millis() % speedLEDBlinkRate < speedLEDBlinkRate/2)
    fill_rainbow(speedLEDs, NEOPIXEL_NUM_LEDS, CRGB::White, 25);

  // The ratio controls the hue of its LEDs
  int ratioLEDHue = map(ratioValue, ratioInputMin, ratioInputMax, 0, 360);
  fill_solid(ratioLEDs, NEOPIXEL_NUM_LEDS, CHSV(ratioLEDHue, 100, 50));
  // We could also blend between 2 colours based on the ratio, pick the one you prefer
  // fract8 ratioLEDFraction = map(ratioValue, ratioInputMin, ratioInputMax, 0, 255);
  // fill_solid(ratioLEDs, NEOPIXEL_NUM_LEDS, blend(CRGB::Blue, CRGB::DeepPink, ratioLEDFraction));

  FastLED.show(); // Shows them
}


// Mozzi's function for getting the sound. Must be as light and quick as possible to ensure the sound buffer is adequently filled
AudioOutput updateAudio() {
  long modulation = aSmoothIntensity.next(fm_intensity) * aModulator.next();
  return MonoOutput::from8Bit(aCarrier.phMod(modulation)); // phMod does the FM
}


// Since we're using Mozzi, we just call its hook
void loop() {
  audioHook();
}

 

Week 8: Reading Reflection of McMillian’s “Her Code Got Humans on the Moon…”

Hi there! 👋

Today, I also wanna reflect on McMillan’s article, “Her Code Got Humans on the Moon — And Invented Software Itself” (on WIRED), which was an interesting article, talking primarily about the history and nature of Margaret Hamilton’s work with NASA on the Apollo missions.

However, one thing I didn’t really like about the article was that it hypes her up a lot, often unjustly and by greatly exaggerating. Make no mistake, I’m not going after her. The work she did was invaluable, and the fact that she worked in a time when women often didn’t get these roles is noteworthy, and I applaud her for that. It’s the article I have an issue with.

For example, the title literally says “Her Code … And Invented Software Itself”, which to anyone familiar with the history of computing would know, is a huge stretch, to say the least. Ada Lovelace is widely believed to have written the very first computer program (software), and hence is the first computer programmer (though some believe Charles Babbage should have this title, as he had some writings written earlier that could be considered programs). So anyways, the title rests between them, not Hamilton, who came over a hundred years later (no knock on her, it’s simply the facts).

Ok, you might be thinking “So what, a news outlet used a clickbaity and incorrect headline, what’s new?” However, it also mentions “Software engineering, a concept Hamilton pioneered” in the article itself, which is also a bit of a stretch, as neither was she the first, nor did she invent the term, which was already in use by her time (although for some credit, maybe not very widely).

Also, while I get that the article focuses on her, it makes it seem like she was single-handedly responsible for saving the mission and some other important aspects, which leaves out the huge collaborative effort and teamwork that went into those missions, as well as a space to call out the other brilliant experts who worked on it as well.

In the end, the article offers an engaging look at Margaret Hamilton’s vital contributions to the Apollo missions (with some feel-good vibes), and while it definitely might’ve overstated her accomplishments, she is highly accomplished regardless, and her, along with the rest of the entire team’s achievements, are something we can all be inspired by and appreciate.

Week 8: Reading Reflection of Norman’s “Emotion & Design: Attractive things work better”

Hey there! 👋

Today, I wanna reflect upon Noman’s essay, “Emotion & Design: Attractive things work better”.

It was a pretty nice and interesting read, that spoke about how design, behaviour, and (a new term for me,) affect, interact. I feel like it takes pieces which we individually can easily accept as true, and ties them together to create more interesting ideas. Overall, he says that how an object is perceived is also dependent on the emotion of the user, as well as the beauty/design (obviously including its workings, but not sorely dependent on it). For example, happy, relaxed users interacting with a beautifully designed product are much more likely to forgiving and overlook some of the faults and issues, compared to focused users in stressful situtations. He ends the main text with title text “Attractive things work better” (an arguably controversial but true statement).

However, while reading this, I couldn’t help but think of some of my previous research (for a different course), where a similar thing could be said for people. Now obviously, this is much more controversial and offensive, but bare with me, research has shown that the “Pretty Privilege” or “Attractiveness Advantage”, where people who are more conventionally attractive have a noticeable advantage in many aspects of life, is unfortunately, very real.

For example, people are slightly more forgiving to those conventionally attractive, not too dissimilar to how people were more forgiving of issues in beautifully designed objects. Also, the halo effect (where a someone associates other positive traits after seeing just a few / one) comes into play too, and since one of the first thing people see when they meet someone, is how they look, that often ends up being the basis (or initial positive trait) for the halo effect, and so those people are usually also considered smart, trustworthy, and kind (among other things).

However, the opposite is also true, and something people who are more conventionally attractive are also considered to be rude, snobbish, arrogant, and undeserving of their success (sometimes for good reason, but not always, and certainly not everyone). Similarly (going back to objects), I think that people also hold more beautiful products to higher standards, and although they might end up being more forgiving sometimes, they could also easily become upset due to expecting it to support their desired functionality (and quite well too), as simply the appearance of a better looking product often leads people to believe it is also better made.

In conclusion, I found this essay to be an insightful reading about how design, affect, and behaviour interact, and how the experience of someone using a product could differ not only on how it works, and not also only about its design, but also the emotional and mental state the user is in, and how the design needs to take that into account. I then compared and contrasted this with how people interact with others (specifically those considered more conventionally attractive), and while drawing similarities between people and objects may seem inhumane, I think it could help us further understand and research these effects, as there’s now another situation that exhibits the same properties, allowing us to look at this problem from another angle.

Week 8: Creative Switch – Course

# Jump To:


# Introduction & Conception

Hey everyone, welcome back from your break! 👋
(I know, I know, I wish it was longer too… but hope you had a relaxing and wonderful break!)

This week’s practical assignment was to create a creative/unusual switch. I immediately got a few ideas in my head, but the one I ended up going with, was a “looking at” detector! (or rather, a very crude head rotation detector 😅). I wanted to activate something something just by looking at / facing it.

So I built a simple circuit with 3 LEDs, that could indicate the rotation of my head (where I was facing). I now remember that initially, I also wanted to extend this concept by having a multiple screen setup, and then automatically closing or blacking out the screens I wasn’t facing towards, saving energy. I also had one where it would automatically dispense food or something when I turned my head, building the ultimate “automated-eating-while-working-solution” (which would probably result in a montage of hilarious fails too) 😂. However, I somehow totally forgot about these, and only remembered while writing this blog post right now (and it’s too late to implement them now, probably for the better 😅). But argh man, those would have made for a much more interesting blog :/… oh well.

 

# Implementation

I first started by connecting 3 LEDs on a breadboard, to 3 resistors which connect to the Arduino’s 3.3V. Then I realised that I could use just 1 resistor, and so wired 3.3V to the resistor, and the resistor to the positive rail of the breadboard. Then I had a wire for each LED’s cathode (the shorter pin, the one that connects to ground), and could now activate any individual LED by connecting the Arduino’s ground wire to the LED’s ground wire! All that was left now was the simply connect ground to a hat (with some tape and foil) and a flag sticking out, and have a foil flag/square for each LED, and success!

 

This circuit is so simple in fact, that it doesn’t even need the Arduino! Literally the only thing the Arduino is doing here is providing power, which can easily be replaced with a battery. However, when I looked at the requirements again, it says we have to use digitalRead :/… Ah man, ok I’ll need to integrate it.

At first I really wanted to keep the simplicity as much as I could, and basically just try the Arduino pins as ground, so there would be almost no difference in the circuit, but we could detect which LED is being lit up. Unfortunately, I read that this is quite a bad idea as the Arduino isn’t well suited for that, so I had to double the number of wires, to separate the switch part from the lighting part. The circuit now still works in a very similar way, it’s just that when I try now connect 2 wires (meaning my head has turned to a certain position), the Arduino detects the switch is activated, and then powers the corresponding LED. Same functionality, but with including the Arduino and its digitalRead and digitalWrite functions. Oh also, I used “INPUT_PULLUP” instead of “INPUT” for the pinMode, as I didn’t want to add another 3 resistors 😅 (so the readings are flipped (as there’s no “INPUT_PULLDOWN” on most of the Arduino boards), 1 for inactive and 0 for active, but this isn’t an issue).

Code:
 

const int rightLED = 10;
const int centerLED = 11;
const int leftLED = 12;

const int rightSwitch = A0;
const int centerSwitch = A1;
const int leftSwitch = A2;

void setup() {
  // Initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  
  // Inputs (set to INPUT_PULLUP instead of INPUT so that it automatically pulls up the value, saving me from adding 3 resistors 😅)
  pinMode(rightSwitch, INPUT_PULLUP);
  pinMode(centerSwitch, INPUT_PULLUP);
  pinMode(leftSwitch, INPUT_PULLUP);

  // Outputs
  pinMode(rightLED, OUTPUT);
  pinMode(centerLED, OUTPUT);
  pinMode(leftLED, OUTPUT);
}

void loop() {
  // Read the input pins
  int rightState = digitalRead(rightSwitch);
  int centerState = digitalRead(centerSwitch);
  int leftState = digitalRead(leftSwitch);

  // Power the correct LED
  digitalWrite(rightLED, rightState == 0 ? HIGH : LOW)
  digitalWrite(centerLED, centerState == 0 ? HIGH : LOW)
  digitalWrite(leftLED, leftState == 0 ? HIGH : LOW)


  // Print out the states
  Serial.print(rightState);
  Serial.print(',');
  Serial.print(centerState);
  Serial.print(',');
  Serial.println(leftState);

  delay(1);  // Short delay between reads for stability
}

 

 

# Final Result

 

 

# Additional Thoughts & Room for Improvement

I initially wanted to use a potentiometer, to be able to get a much greater resolution and more accurately determine where the head was facing (and hence, have more fine grained control over what activates), while also eliminating a few wires and the need to have the foil flags. While I opted out of that idea as this assignments requires we use digitalRead, it would be a great improvement.

Well, unfortunately that’s it for today (yea, this was a very short blog), so until next time!

Week 6: Midterm Project – Superimpose

# Jump To:


Update: Fixed the link, as it turns out, it was broken this entire time!
(wait, so you’re telling me that no one got to play my game? 🙁)


# Introduction & Project Concept

Hey everyone, welcome back! 👋

In this blog, I’ll be talking a little bit about my midterm project. Unfortunately (or fortunately, depending on how much you want them), this blog post isn’t going to be as detailed as my usual ones. Instead, it’ll just be a very cursory overview of a few parts of the project.

So, what is my project? Well, it’s a game where you control a character using your own body (pose detection), and you have to try to fit through the target pose cutouts, similar to the ones you see in cartoons when a character goes through a wall.

Human shaped hole in wall

Though, since the poses are detected using a webcam, it can be hard to get the distance and space required to detect the whole body (and also still clearly see the screen), so instead the cutouts are only for the upper body, which should make it accessible to and playable by more people.

 

 

# Implementation Details

## How it Works (in a Nutshell)

The core mechanic of the game is the player’s pose. Thankfully, this can be detected relatively easily with the help of ml5.js’s BodyPose, which after integrating, lets you know the locations of each of the keypoints. From the 2 models available (Movenet and BlazePose), I choose Movenet, and you can see the the keypoints it detects below.

 

MoveNet keypoint diagram

 

Obviously, the keypoints the camera can’t see won’t be very accurate at all, but the model will still report them. Thankfully, it also reports its confidence, and so you can easily filter out the keypoints which don’t have a certain threshold confidence.

In terms of why I choose MoveNet, it’s because it is the newer model, and was built for faster recognition, which is important in a game like mine (you obviously don’t want players to notice or feel a lag in their movements, which makes it harder to control and less enjoyable) (though to be honest, BlazePose would work fine here too, it would just be a bit slower and laggier). Also, another one of the parameters I specified was the type (SINGLEPOSE_THUNDER), which means that it should only track 1 person, and with slightly higher accuracy.

Anyways, so we got our pose, and I drew it on the screen. Then, the player sees a target pose cutout (which was generated by creating a random pose, and erasing those pixels from the surface), and tries to match it (hopefully, you don’t wanna crash into it!). Once the target is close enough, we check if the player’s pose matches the target cutout’s pose. Now, since it’s nearly (or literally) impossible to match it exactly to the subpixel (in this case, 13 decimal places smaller than a pixel!), especially for every point, we just check whether the pose’s are close enough, by adding a bit of margin to point and checking if the keypoints are within that margin. If it matches, hurray! The player scores a point and the target disappears (only for another one to come out later… 😈, ahem 😇). Otherwise, the target still disappears, but instead of scoring a point, they lose a heart/life. If they’ve lost too many, the game ends.

That basically sums up the main game loop.

Now, this is all implemented in an OOP manner, so there are classes for each scene, and for any objects that would benefit from being a class. So, for scene management, I have an object/dictionary containing a reference to all the scene objects instantiated from their respective classes, and field that holds a reference to the current scene (or alternatively a variable that keeps track of the current scene’s name).

Similarly, I also have an object/dictionary containing all the sounds and images to be used (which get loaded in preload, like the pose detection model). Other than that, the rest is mostly similar to other p5.js projects.

 

## Some Stuff I Liked

This may seem small, but one of the parts I like is my random pose generation. Since it is, you know, a core part of the gameplay (the pose for the target cutouts), I had to get this done fairly well. While I initially thought of using random offsets from a set pose, or even a set of custom poses I did (which would save a lot of time), I knew this solution wouldn’t be the best, and would have several issues (for example, others will likely have a different height and body segment lengths, as well as from different distances and angles to the camera). Instead, my current solution accounts for most of that, and is made it from a system of constraints.

Basically, I first measure some key lengths from the player (such as the distance between the wrist and the elbow, elbow to the shoulder, shoulder to the hip, original y-value of nose, etc). Then I first start by generating random coordinates for the nose, which are in the center 25% (horizontally) of the screen, and within 25px or so (vertically) of the player’s original nose height (btw, so instead of generating several points for the head (eyes, ears, & nose), like the model outputs, I can just treat the nose as the center of the head, which is much simpler and surprisingly doesn’t have almost any drawback for my usecase, so I do that). After this, I get a random angle between -30 to 30 degrees for the shoulder midpoint, and then calculate the shoulders from there (taking into account the player’s shoulder to shoulder length, and nose to shoulder midpoint length). Similarly, I also calculate a random angle in a certain acceptable range, and use the user’s segment length to calculate the exact position. Now, I also want to ensure that no part goes offscreen, or even within 10% of the edges, so I wrap the generation of each part in a do… while loop, which ensures that if a certain part does get generated too close to the edge, it tries again, calculating new positions from new random but constrained values. Additionally, I also don’t want any of the points to be too close to each other (which could especially be an issue with the wrists, both to each other and to the head).

But what if it is literally impossible to satisfy all these constraints for a certain part? Then the program will just keep trying and trying again, forever, which we obviously don’t want. So I keep a track of the number of current attempts, and if it goes above a certain threshold (say 10), then I start from scratch, and return a completely brand new random pose, using recursion (hoping that the new system won’t run into similar situations too many times, which thankfully is the case, as it’s quite rare for it to retry). I also keep a track of the number of attempts for the completely new pose, and if it gets too high, then I just return the default pose (this is more so just to be extra safe, since thankfully, this is almost never going to happen, as there is an extreeeemeeellyyyy small chance that it bails out and fails trying to return a completely new pose, that many times).

So, that’s it! Despite being really quite simple, it’s pretty effective. You can see the code for it, and try it out in the sketch, below (it’s actually a bit unnerving and intriguing how we can associate actions and feelings, just by looking at random poses made with simple rules).

 

// Initialised with some default values (roughly my measurements)
let playerBodyInfo = {
	"nose y-value": 165,
	"nose-shoulder midpoint length": 50,
	"shoulder midpoint-hip midpoint length": 150,
	"shoulder-shoulder length": 90,
	"shoulder-elbow length": 70,
	"elbow-wrist length": 60
}

// lots of other code ... (not relevant for this)

// method within Game class
generateRandomPose(attempts = 0) {
	// Constraints / Ideas / Assumptions:
	//	- Nose should be in the middle 25% (horizontally) of the screen, and near the height of the player's nose originally (similar y-value)
	//	- 0 deg <= midpoint-shoulder-elbow angle (inside one) <= 180 deg (basically, the elbow should be outside the body, extending upwards)
	//	- 45 deg <= shoulder-elbow-wrist angle (inside one) <= 180 deg
	//	- All parts should be within the center 80% (the nose and shoulders don't need to be tested, since they can't reach there anyways)
	//	- Also, parts shouldn't be too close to each other (realistically the thing we need to check for is wrists to each other and the nose)
	//	- First generate nose position (center of head), then shoulders, then so on.

	let outerMargin = 0.1; // 10%, so points should be in the middle 80% of the detection area (webcam feed)
	let minX = webcamVideo.width * outerMargin
	let maxX = webcamVideo.width * (1 - outerMargin)
	let minY = webcamVideo.height * outerMargin
	let maxY = webcamVideo.height * (1 - outerMargin)
	let partAttempts, leftShoulderToElbowAngle, rightShoulderToElbowAngle, leftElbowToWristAngle, rightElbowToWristAngle

	// Initialised with some default values (roughly my measurements)
	let pose = {
		nose: {x: 320, y: 165},
		left_shoulder: {x: 275, y: 215},
		right_shoulder: {x: 365, y: 215},
		left_hip: {x: 295, y: 365},
		right_hip: {x: 345, y: 365},
		left_elbow: {x: 220, y: 255},
		right_elbow: {x: 420, y: 255},
		left_wrist: {x: 200, y: 200},
		right_wrist: {x: 440, y: 200}
	}

	// If it takes too many attempts to generate a pose, just give up and output the default pose
	if (attempts > 100) {
		print('Pose generation took too many attempts, returning default pose.')
		return pose
	}


	// Nose

	pose.nose.x = random(0.375, 0.625) * webcamVideo.width // center 25%
	pose.nose.y = random(-25, 25) + playerBodyInfo["nose y-value"] // y-value +- 25px of player's nose height


	// Shoulders

	let shoulderAngle = random(-PI/6, PI/6) // The angle from the nose to the shoulder's midpoint with origin below (think of a unit circle, but rotated clockwise 90 deg) (also equivalently, the angle from the left to right shoulder, on a normal unit circle). From -30 to 30 degrees
	let shoulderMidpoint = {
		x: pose.nose.x + sin(shoulderAngle) * playerBodyInfo["nose-shoulder midpoint length"],
		y: pose.nose.y + cos(shoulderAngle) * playerBodyInfo["nose-shoulder midpoint length"]
	}
	
	pose.left_shoulder.x = shoulderMidpoint.x - cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.left_shoulder.y = shoulderMidpoint.y + sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	
	pose.right_shoulder.x = shoulderMidpoint.x + cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.right_shoulder.y = shoulderMidpoint.y - sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	

	// Hips

	let hipMidpoint = { // The hip's midpoint is really just the shoulder's midpoint, but extended further, so we can calculate it in a similar fashion
		x: pose.nose.x + sin(shoulderAngle) * (playerBodyInfo["nose-shoulder midpoint length"] + playerBodyInfo["shoulder midpoint-hip midpoint length"] + 50*0), // [Nvm, disabled for now] Added 50 in the end, to ensure it's long enough (I'm not using the hips for accuracy or points, but rather just to draw the outline)
		y: pose.nose.y + cos(shoulderAngle) * (playerBodyInfo["nose-shoulder midpoint length"] + playerBodyInfo["shoulder midpoint-hip midpoint length"] + 50*0) // (as above ^)
	}
	
	pose.left_hip.x = hipMidpoint.x - cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.left_hip.y = hipMidpoint.y + sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	
	pose.right_hip.x = hipMidpoint.x + cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	pose.right_hip.y = hipMidpoint.y - sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
	

	// Elbows

	partAttempts = 0;
	do {
		if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		leftShoulderToElbowAngle = random(PI/2, 3 * PI/2) + shoulderAngle // From 90 to 270 (-90) degrees on a normal unit circle (basically 0 to 180 degrees, with the left half of a circle (imagine the unit circle rotated anticlockwise 90 deg))
		
		pose.left_elbow.x = pose.left_shoulder.x + cos(leftShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
		pose.left_elbow.y = pose.left_shoulder.y - sin(leftShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
		
	} while (
		minX > pose.left_elbow.x || pose.left_elbow.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.left_elbow.y || pose.left_elbow.y > maxY // Check if it's within the acceptable verticle range
	);
	
	partAttempts = 0;
	do {
		if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		rightShoulderToElbowAngle = random(-PI/2, PI/2) + shoulderAngle // From 270 (-90) to 90 degrees on a normal unit circle (basically 0 to 180 degrees, with the right half of a circle)
		
		pose.right_elbow.x = pose.right_shoulder.x + cos(rightShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
		pose.right_elbow.y = pose.right_shoulder.y - sin(rightShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
	
	} while (
		minX > pose.right_elbow.x || pose.right_elbow.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.right_elbow.y || pose.right_elbow.y > maxY // Check if it's within the acceptable verticle range
	);


	// Wrists

	partAttempts = 0;
	do {
		if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		leftElbowToWristAngle = random(1.25*PI, 2*PI) + leftShoulderToElbowAngle // random(PI/4, PI) // From 45 to 180 degrees on a normal unit circle. Will be rotated to account for the elbow's existing rotation 
	
		pose.left_wrist.x = pose.left_elbow.x + cos(leftElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
		pose.left_wrist.y = pose.left_elbow.y - sin(leftElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]

	} while (
		minX > pose.left_wrist.x || pose.left_wrist.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.left_wrist.y || pose.left_wrist.y > maxY || // Check if it's within the acceptable verticle range
		dist(pose.nose.x, pose.nose.y, pose.left_wrist.x, pose.left_wrist.y) < 50 // Check if the wrist is too close to the nose ); partAttempts = 0; do { if (++partAttempts > 10) return this.generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
		
		rightElbowToWristAngle = random(0, 3/4 * PI) + rightShoulderToElbowAngle // From 270 (-90) to 90 degrees on a normal unit circle (basically 0 to 180 degrees, with the right half of a circle)
	
		pose.right_wrist.x = pose.right_elbow.x + cos(rightElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
		pose.right_wrist.y = pose.right_elbow.y - sin(rightElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]

	} while (
		minX > pose.right_wrist.x || pose.right_wrist.x > maxX || // Check if it's within the acceptable horizontal range
		minY > pose.right_wrist.y || pose.right_wrist.y > maxY || // Check if it's within the acceptable verticle range
		dist(pose.nose.x, pose.nose.y, pose.right_wrist.x, pose.right_wrist.y) < 50 || // Check if the wrist is too close to the nose
		dist(pose.left_wrist.x, pose.left_wrist.y, pose.right_wrist.x, pose.right_wrist.y) < 50 // Check if the wrist is too close to the other wrist
	);

	return pose;
}
 

(Click inside the sketch, then press any key to generate a new random pose)

 

Another part I liked was the illusion of perspective. So basically, I had to rush this project, so I thought, “Fine, I’ll do it in 2D, that’s much simpler”, and it is, but I still wanted that 3D perspective effect 😅. After playing around for a bit in a new temporary sketch, I found out that if I scale everything from the center, then the 2D sketch appears to have (a very basic, rudimentary, and probably physically inaccurate, but nonetheless, visible) perspective! While the best and fully implemented version is in the final game, you can see that temporary sketch below.

(Click inside the sketch, then press r to restart, or any other key to toggle between the first and second test (now basically just uncoloured vs coloured)… oh also, please, you know what I mean by “any other key”, so don’t press the power button or something 😂)

 

## Some Issues I Encountered

Oh boy oh boy, did I face several issues (as expected). Now, I can’t go over every issue I faced, so I’ll just mention a couple.

First one I’ll mention, is actually the random pose generator mentioned above! Yep, while I did end up getting it working (and well enough that I liked it and included it in my good parts section), and while it is conceptually pretty simple, it still took a fair bit of time (wayy longer than I thought it would, or even still think it needs to), and was a bit tricky, particularly with working out the angles and correct sin and cos transformations (oh, I messed up the signs more than once 😅). In fact, I actually made the sketch above to quickly test out my pose generation! I had written it all “blind” (aka without testing in between), and since it was fairly straightforward, I was confident it would work, but something nagged me to just try it once before the seeing it in the main game, and… yep, it didn’t work. *sighs*. I had to meticulously comment out and retry each portion and work back to a full solution, bit by bit. Fortunately it wasn’t too conceptually difficult (more time consuming than hard, but even the basic trig was a little rough tough at 3 AM), so I succeeded.

Another issue I faced was that the target cutouts, didn’t have any actual cutouts(!), which is, you know, a major issue. Again, I broke out a new temporary sketch to test out my logic, simplifying stuff and working things out. It turned out to be a simple issue of using the screen’s width instead of the webcam’s width, and a few other things. In case you’re wondering, yep, the screen and webcam not only have different resolutions, but also different aspect ratios! It was a bit of a pain initially to get it so they worked seamlessly together, but now I have. The reason behind this, is that some webcams (particularly those on laptops) give a 4:3 image for some reason, and since I need to run a machine learning model (MoveNet, for the poses), they usually reduce the resolution required (otherwise it would take a LOT longer to detect the pose, which would break the game). I wanted the game’s output on the other hand to be a crisp (scalable up or down but ideally) 1920×1080, 16:9 aspect ratio, hence the mismatch.

(It’s not really interactive (besides f for fullscreen), so don’t bother ;) )

Well anyways, that’s it for now (I think I might’ve made it a bit longer and more detailed than originally thought 😅), so without further ado, I present, my ✨midterm project✨! (I should really have a name for it, but I can’t decide 🤦‍♂️)

# Final Result – Midterm Project

While I would normally embed the sketch here, I think you should really open it full screen in a new tab, so follow this link.

# Additional Thoughts & Room for Improvement

With any project, there’s always room for improvement. But for this one specifically, there’s a huge room for improvement 😅 (mainly because I keep seeing new features to implement or ideas to improve it). As usual, there are a bunch of things I didn’t get time to implement or do, ranging from better graphics and polish, to levels and features. I particularly wanted to implement a character that actually goes ahead of you (that you are trying to catch) that “breaks” the walls (causes the cutouts), and also a level that took place in a top secret laboratory setting, where the character is actually stealing some top sneaky info, and so you have to stop them. I kind of really want to continue this and flesh it out (but I also know it’ll not be of much use, and besides, p5 isn’t the best platform to implement this idea, so why waste time here… so anyways *cuts to me working on this months into the future* 😅).   Well, I’m really glad you went through the entire post (and definitely didn’t skip till the final result), but regardless, thanks a lot for reading it (does anyone even read these, or am I just talking to myself? ._. ). Unfortunately, that’s all I’ve got time for, so we’ll have to end it here. Until next time, and enjoy your break!   https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExd3B2ZGduZjBpYXNsa2F6bmxqZjg5dTFjbnE0bWR1czNiZ3FxYzc4OSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/EbOLm8dEE2OrBh68H7/giphy.webp

(psst, do you know what time his watch shows? Ok, fine, it isn’t wearing one, but if it did, it would show) https://i.giphy.com/S8DcNuvt1FUy31LUH6.webp

Week 5: Midterm Progress

# Jump To:


 

# Introduction

Hey everyone! 👋

Like probably everyone else on the entire planet, I wasn’t really sure what to do for my Intro to IM Midterm Project at NYUAD (ok fine, maybe not everyone else) (fun fact, I’m still not certain 😂😭). I was deliberating whether to stick with traditional input devices (mouse and keyboard), or go for some interesting new (like the classic and probably cliche, face or body tracking). Unfortunately, I don’t own a guitar like Pi Ko, so I guess I’ll have to stick with something traditional 😅 (now that I think about it, I could use a piano, wait a moment…)

 

# Piano Game Concept

Temp Piano Game Concept, with keys controlling the height of the bars, and a ball on it
A piano game concept!…
Gosh… why do I waste so much time, on a joke concept…

So the keys of the piano (but optionally also the keyboard, allowing everyone to play it) control the height of the bars, and you have to get a ball that drops out of the start area into the goal, potentially avoiding obstacles and overcoming bad physics, all the while automatically producing (probably ear piercing) music random garble of sounds. That’s a win-win-win!

Ok, this was a joke. Now back to the actual concept I wanted to talk about 😅 (though to be honest, this seems more original and fun ._.)

 

# Actual Game Concept

You know those outlines they have of people (like at crime scenes, but don’t think about that)?

Body outline

Or those ones where a cartoon character goes through a wall?

Human shaped hole in wall

 

Well, what if you had to make the correct pose to fit through a series of holes and avoid hitting the wall? That’s the essence of my idea. I thought about it while thinking on how I could utilise face/body tracking (which probably shows 😅), and which is exactly the wrong approach (you’re supposed to first have an issue/idea, then think about how to solve it, not try to find a use case for a certain techonology, that’s like a solution in search of a problem). Also, this idea is simple and obvious enough that while I haven’t seen it yet, it very well might already be a real thing. Still, I find the idea quite charming, especially as I envision it on a large screen, with people frantically and hilariously jumping around positions. I will also include a “laptop mode”, where the program only shows upper body cutouts, in order to make it accessible and playable on laptops too (where it would be hard to get the distance required for the camera to see the full body, while still allowing you to comfortably see the screen).

It should not come as a surprise then, that I plan to use body tracking to be able to control the motion of the character. Of course, it would be a gargantuan task to implement a decent body tracking solution from scratch (something more for a final year’s or even PhD’s research project than an Intro to IM midterm), so I will use a pre-existing library to handle the detection for me, mainly movenet (probably through ml5.js), a fast and accurate pose detection model by Google.

As I was thinking about this a bit, I began thinking that maybe this could be about a ninja’s training. You enter the secret facility (the interface is hopefully interactable through hand gestures, similar to the Xbox with kinect, if I have enough time), and then have to undergo the “lightning quick attack & defense poses” (or something), as part of your training.

 

## Complex part & Risk Reduction

As part of our midterm progress blog post, we have to identify the most complex/frightening part, and do something to tackle and reduce that risk. For this project, it is obviously the body tracking, so I created a new sketch to test out the library, and coded together a simple barebones concept to ensure the tracking works, and if I can reliably assess whether a person is in the correct pose. In addition to the pose detection, it also displays a skeleton of the person (of the detected points), and I made it slightly show the webcam’s video feed (both to help the player adjust their position). Also, it shows a (faint) guide to the target pose.

Mini rant:

I first made an image for the target pose, but I’m still somehow unable to upload anything to p5, despite trying with different browsers, at different times, and even different accounts, since the start of the semester (yep, I haven’t been able to upload anything at all). Luckily in this case, it was just a simple image, so I drew it manually with p5.js (which is better in fact, since the image automatically updates if I change the target pose), but still, this is extremely annoying and limiting. If you know any solutions, please let me know.

 

Try and see if you can match the pose!

 

(Hint: It tests for the shoulders, elbows, and wrists. Also, try moving back and forth if the frame doesn’t fit, and try changing the lighting if it isn’t detecting you properly (ignore the flickering, that’s a known issue))

It works! Something I don’t like is the constant flickering, but I think I might be able to mostly solve that (at the expense of slower update times, by using a moving/sliding average), so I would consider this a success!

Week 5: Reading Reflection of “Computer Vision for Artists and Designers”

Hey there! 👋

I was reading this great article about Computer Vision for Artists and Designers, which mainly talks a bit about the history of computer vision, interactive media using computer vision, and some basic computer vision techniques.

It made me think about an interesting comparison, of the difference between our vision, and that of a computer’s. While the structural and architectural differences are obvious (biological vs technological), a more interesting comparison point lies in the processing. Video, as the article mentioned, doesn’t contain any inherent, easily understood meanings. Instead, in both cases, it is up to the processing to extract key information and figure out the required details. While such a task is usually easy for our brain, trivial in fact, is an incredibly difficult problem to overcome, especially from scratch. It might seem easy to us, because our brain is one the most complex things in the universe, and also had a huge head start 😂, allowing it to develop specialised pathways and processes capable of carrying out this operation in a breeze, which is understandable as vision is an extremely important tool in our survival. Computer vision meanwhile, is starting in this gauntlet of a task, nearly from scratch. While we do not have to evolve biological structures and compete on our survival, there is still a very real challenge, though thankfully, we can progress on this frontier much quicker than evolution or nature could (that big brain of ours coming in handy again. Wait, if you think about it, we are using our eyes and brain, to replace, our eyes and brain… and our entire body and self, in fact).

There are several methods of implementing different aspects of computer visions. Some basic ones, mentioned in the article, include detecting motion (by comparing the difference in the current frame with the previous one), detecting presence (by comparing the difference in the current frame with one of the background), brightness thresholding (just checking if a pixel is lighter or darker than a certain threshold), and rudimentary object tracking (usually by finding the brightest pixel in the frame), though many other basic techniques also exist (eg. such as recognising what a picture is by comparing against a stored collection of images). These basic implementations however cannot stand much alternation in their expected environment, and even something relatively simple (like the background changing colour, perhaps by a shadow being cast) would render them ineffective. Also, trying to implement a task as vast as computer vision by precise, human defined algorithms, is extremely hard (I know all algorithms are technically precisely defined, so what I mean by this is that we have specifically coded in behaviours and processes).

A far more successful approach (like in many other fields from aerodynamics to energy efficiency), has been to try and somewhat copy what nature does (though on a simpler scale). “Recent” approaches like neural networks, and other machine learning techniques, have begun far outperforming anything a precisely defined algorithm could do, on most real world tasks. The simplest structure of a neural network, is to have neurones, or nodes, connected to other nodes, and the value is simply modified as it travels from node to node (vast oversimplifying), mimicking the model of the neurons in a brain (though in a much more basic representation). The beauty of such approaches is that they leave the specifics undefined, allowing the model and training process to improve itself, automatically choosing a somewhat good selection of processes to extract meaningful information (when done right). Of course, this doesn’t mean that this is easy, or that one a neural network is made, all the problems can be solved by simply feeding it enough data – innovation in the architecture of the neural networks themselves (e.g. the introduction of U-nets, LTSM, transformers, etc) and surrounding ecosystem must also happen – but it does allow a different way of doing things, which has so far yielded fruitful results (checkout any of the vast number of computer vision libraries, including PoseNet, YOLO, and probably most importantly, OpenCV).

Though the advancements in computer vision are coming ever faster, and many people around the globe dream about being able to control things with just their body and vision (probably in no small part due to movies showing cool futuristic tech), the use of computer vision, in anything, is still a heated discussion. On one hand, widely implementing it could provide alternative pathways to accomplish tasks for many people who might otherwise not be able to do it, increasing accessibility, and it also enables several use cases which before were never even thought to be remotely possible (and also, it’s just cool!). On the other hand however, as with any technology, we have to acknowledge that the vast capabilities of such a system could also be used for nefarious purposes. In fact, governments and corporations are already misusing the capabilities to carry out dystopian practices on an unprecedented scale, widely being in mass surveillance, targeted tracking, selling user info, avoiding responsibilites (eg. car manufacturers on warranty claims), and so much more, all for power and profit (you might think I’m exaggerating a little, but no, look it up. The rabbit hole goes extremely deep (and I’d be happy to chat about it 🙂 )). As a certain spidey’s uncle once said, “With great power comes great responsibility”, and currently, we can’t be sure that those with that power won’t abuse it, especially as they have already routinely been.

With such a conundrum of implications and possibilities, computer vision’s use in interactive art is no doubt a little damper, with it predominantly being featured in places where it is easily and visibly stopped/escaped, such as galleries and public (but not too public) and private installations, and apps and websites users trust, vs throughout every aspect of our lives (though it is undoubtedly hard to fully trust an app/website, and that trust can be easily broken, though of course, not everyone is as weary of these technologies).

Week 4: Reading Reflection of Don Norman’s “The Design of Everyday Things”

Hey everyone! 👋

After reading Don Norman’s “The Design of Everyday Things”, I was reminded to take a look around me, and find out other examples of bad user experience. One of the first examples that comes to mind, is from my short time at NYUAD’s dorms. As you can see, there are 2 blinds in the same window, one partially translucent, and one fully opaque.

NYUAD's blinds
NYUAD’s blinds

While the mechanism to control a particular blind is simple enough, and a common enough pattern for us to intuitively use it, what is extremely confusing is which string controls which blind, as they are both identical! Also, it seemed liked different windows had a different arrangement/side of strings controlling each blind! My  solution to fix this, would be to have the opaque one keep it’s metal beads, but to switch the translucent one’s beads to like a white plastic. This way, you can both see and feel the difference between the 2 strings, allowing you to easily choose which blind to control. Additionally, they can be in any order/side, but it must be consistent for all the windows, allowing you to develop muscle memory.

Another example, though not affecting everyone, is USB. You might think I’m referring to the USB A style connectors, that somehow always take 3 tries to get in.

USB A, 3 tries and states
I didn’t know they exhibited quantum properties!

No, that has mostly been solved with USB C (ah, a godsend). Unfortunately, USB C has introduced it’s own complications, by becoming the universal standard!

Wait, a minute, that doesn’t make sense. Isn’t that a good thing?

Well mostly yes, but this also means that USB C’s capabilities have far expanded outside the original scope of a USB connector, allowing the transfer of not just power and files (each at different levels), but also HDMI, DisplayPort, Thunderbolt, Ethernet, and so much more. The result is an incredibly convenient ecosystem and user experience, where everything fits together seamlessly… as long it works. The issue isn’t USB C’s reliability, but rather that since it supports so many protocols and extensions, almost all of which are optional, it can be quite hard to know which cable or device (or even port on the same device!) supports which features (this could  supporting a protocol, like DisplayPort, or a certain power and speed output). As a result, it often leads to frustration, realising only after a long time wondering why it isn’t working, that the cable I have in hand is inadequate for the task.

The solution I would recommend, would be to support everything that makes sense, and the highest speed & power level. Although, of course I would say that, I’m the user! It’s true that going this route is going to considerably increase costs for manufacturers, though as Don Norman said, poor design is sometimes due to decisions aimed at reducing cost, and this is certainly an example of that, so maybe it would be worth it for manufacturers to implement it, at least for our sanity.

Note:

Obviously, the capabilities supported should be within reason. FOr example, it makes no sense for a router which lacks any direct graphical interface, to support HDMI or DisplayPort in their USB C ports. Also, there does exist a standard that supports almost everything, and the best power & speed levels, which makes it much easier to know what works, Thunderbolt Thunderbolt icon. However, it is usually pretty expensive and isn’t offered/supported on many devices. Oh also, USB’s naming needs to be fixed. They have changed it so many times, leading to a massive amount of confusion. Search it up.

 

Coming to second part of our prompt, I believe the main way we can apply his principles, is by integrating discoverability, affordances, & signifiers, with deliberate thought. Namely, we should make sure that we strive towards adapting our work around the person, having intuitive design, where the user doesn’t need to read an instruction manual (or ideally, read any instructions at all, possibly by using familiar visual elements and design practices people have already gotten accustomed to) to interact with our work. It should instead nudge or guide the user subtly (if required), and perhaps most importantly, provide immediate (and satisfying) feedback, and provide the space for people to make mistakes, but gracefully get back on track.

They should enjoy interacting with our work, not be afraid or overwhelmed by it that, so that it doesn’t end up like the unfortunate (but probably technically great) Italian washer-dryer combo 😉

Sad washer-dryer
A sad, abandoned boi, forever waiting for someone to enjoy using it🥺

Week 3 – Reading Reflection of Chris Crowford’s “The Art of Interactive Design”

Hi everyone! 👋

Chris Crowford’s “The Art of Interactive Design”, was certainly an interesting read. He takes a rather more strict approach to interactivity, perhaps due to the blatant misuse of the word on things undeserving to be called interactive, which in turn potentially makes it more useful, especially as the meaning is easier to grasp. By distilling it into 3 clear parts (listening, thinking, and speaking), it makes it easier for designers to assess where their piece could potentially perform better.

Although it seems a bit controversial, to be honest, I quite agree with his definition, but I can easily see how others might not. Many common forms of interactive media are dismissed by him, but he backs it with a point I liked, a distinction between “interactive” and “intense reaction”, which can so often be blurred.

Another point I really liked was “No Trading Off”. Previously, I would’ve assumed that if 2 parts of his interactive design’s defintion were done really well (while it wouldn’t be the same as all 3 being done well), it would come pretty close. However, he claims this is not the case, and it is a logical thing to believe.

Ultimately, I feel like his definition is most relevant to us in helping us better plan the components of our design, ensuring that it “listens”, “thinks”, and “speaks” well. This is something I could really use, as I often spend too much in the “thinking” and “speaking” part, designing something technically incredible, but ultimately, not as interactive.

 

One off-topic point, I like his style of writing, as it’s less formal / more personal, which makes it more enjoyable to read (particularly things like his explanation on books & films not being interactive, or the questions).