GraIce Final Prototype | IAT 320
Team
Project Concept: SooJin Lee
Costume Design and Sewing: SooJin Lee
Sewing Assistants: Ryan Jeon, Jasmine Han
Coding and Testing: Ryan Jeon, SooJin Lee
Presentation Slide, Demo Video: Jasmine Han (Director), Ryan Jeon
Sewing Assistants: Ryan Jeon, Jasmine Han
Coding and Testing: Ryan Jeon, SooJin Lee
Presentation Slide, Demo Video: Jasmine Han (Director), Ryan Jeon
What is GraIce?
GraIce is an interactive garment for male dancers. The inspiration for the project came from male figure skaters and their hand/limb gestures used during step sequence especially. Our wearable technology recognizes tilting and moving of user's body and sends out sparkling LED lights as feedback. As for the LEDs implemented around the waist, it captures sound frequency and displays it as LED lights. The colour of lights goes from red to white to and turns on from sides of user's body to the front of the torso. The sound sensor captures the music used for figure skating and converts it in the LED lights displaying intensity of music and the beat.
Aesthetics
Inspired by men's and women's fashion from 1800s to 1900s, GraIce costume mixes masculine and feminine qualities together. The emphasis on waist to resemble corset and usage of lace expresses feminine quality while the dark colours and beads layout inspired by embellishments on military coat to communicate the old value of what masculinity used to be.
Individually, I worked on the final form and aesthetic components of the outfit. As a team, we worked on the wiring and the placement of technology on the garment.
Consideration of different bead patterns. We later learned that bead could affect the conductive thread stitches since beads contained conductive material. The placement of beads on the garment needed to be considered in order for the tech components to work without any interference to the circuit.
First Costume Design Idea
Past Costume Design Considerations
We tried to emphasize the arm area and the waist area.
After figuring out all the technology and the overall aesthetic look of the costume, I sketched out the patterns we need for the project.
Technical Aspect & First Prototype
Materials used: LEDs, neopixels, Adafruit flora board, accel/gyro sensor, sound sensor, organza, and dark fabric.
After testing with lux sensor and neopixel LEDs, we decided that the best choice of technology to use was to have two sets of LED strip with different sensors in each set. Originally, we wanted to use LED strip with our sound sensor but due to large battery voltage requirement, we decided to go with simpler setup to consider the total weight of the garment. This decision can affect comfort of the wearer because figure skater has to be flexible and comfortable enough to perform poses requiring stretching of limbs and extreme movements.
Setup 1. Gyroscope + Neopixels
Setup 2. Sound Sensor + Neopixels.
Gyroscope test with two neopixels on same digital pin.
Concept sketches for mapping out where to sew the tech components on the clothing.
Different options for layering tech and sleeve fabrics.
Arduino Code for Gyroscope
#include <Wire.h>
#include <Adafruit_LSM9DS0.h>
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, 10, NEO_GRB + NEO_KHZ800);
Adafruit_LSM9DS0 lsm;
uint8_t myFavoriteColors[][4] = {{0, 117, 255}, // blue
{200, 200, 200}, // white
{100, 149, 237}, //Cornflower Blue
};
#define FAVCOLORS sizeof(myFavoriteColors) / 3
// lower number = more sensitive
#define MOVE_THRESHOLD 700
void setup()
{
Serial.begin(9600);
#include <Adafruit_LSM9DS0.h>
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, 10, NEO_GRB + NEO_KHZ800);
Adafruit_LSM9DS0 lsm;
uint8_t myFavoriteColors[][4] = {{0, 117, 255}, // blue
{200, 200, 200}, // white
{100, 149, 237}, //Cornflower Blue
};
#define FAVCOLORS sizeof(myFavoriteColors) / 3
// lower number = more sensitive
#define MOVE_THRESHOLD 700
void setup()
{
Serial.begin(9600);
if (!lsm.begin())
{
// Serial.println("Oops ... unable to initialize the LSM303. Check your wiring!");
while (1);
}
strip.begin();
strip.show(); // Initialize all pixels to 'off'
}
void loop()
{
// Take a reading of Gyroscope data
lsm.read();
double storedVector = lsm.gyroData.x*lsm.gyroData.x;
storedVector += lsm.gyroData.y*lsm.gyroData.y;
storedVector += lsm.gyroData.z*lsm.gyroData.z;
storedVector = sqrt(storedVector);
// Serial.print("Len: "); Serial.println(storedVector);
// wait a bit
delay(100);
// get new data!
lsm.read();
double newVector = lsm.gyroData.x*lsm.gyroData.x;
newVector += lsm.gyroData.y*lsm.gyroData.y;
newVector += lsm.gyroData.z*lsm.gyroData.z;
newVector = sqrt(newVector);
// Serial.print("New Len: "); Serial.println(newVector);
// are we moving
if (abs(newVector - storedVector) > MOVE_THRESHOLD) {
// Serial.println("Twinkle!");
strip.setBrightness(100);
flashRandom(1, 3); // first number is 'wait' delay, shorter num == shorter twinkle
flashRandom(5, 7); // second number is how many neopixels to simultaneously light up
flashRandom(3, 12);
} else {
strip.setBrightness(30);
colorWipe(strip.Color(88, 11, 28), 175); // wine red
colorWipe(strip.Color(133, 39, 78), 200); // cherry
}
}
void flashRandom(int wait, uint8_t howmany) {
for(uint16_t i=0; i<howmany; i++) {
// pick a random favorite color!
int c = random(FAVCOLORS);
int red = myFavoriteColors[c][0];
int green = myFavoriteColors[c][1];
int blue = myFavoriteColors[c][2];
// get a random pixel from the list
int j = random(strip.numPixels());
//Serial.print("Lighting up "); Serial.println(j);
// now we will 'fade' it in 5 steps
for (int x=0; x < 5; x++) {
int r = red * (x+1); r /= 5;
int g = green * (x+1); g /= 5;
int b = blue * (x+1); b /= 5;
strip.setPixelColor(j, strip.Color(r, g, b));
strip.show();
delay(wait);
}
// & fade out in 5 steps
for (int x=5; x >= 0; x--) {
int r = red * x; r /= 5;
int g = green * x; g /= 5;
int b = blue * x; b /= 5;
strip.setPixelColor(j, strip.Color(r, g, b));
strip.show();
delay(wait);
}
}
}
{
// Serial.println("Oops ... unable to initialize the LSM303. Check your wiring!");
while (1);
}
strip.begin();
strip.show(); // Initialize all pixels to 'off'
}
void loop()
{
// Take a reading of Gyroscope data
lsm.read();
double storedVector = lsm.gyroData.x*lsm.gyroData.x;
storedVector += lsm.gyroData.y*lsm.gyroData.y;
storedVector += lsm.gyroData.z*lsm.gyroData.z;
storedVector = sqrt(storedVector);
// Serial.print("Len: "); Serial.println(storedVector);
// wait a bit
delay(100);
// get new data!
lsm.read();
double newVector = lsm.gyroData.x*lsm.gyroData.x;
newVector += lsm.gyroData.y*lsm.gyroData.y;
newVector += lsm.gyroData.z*lsm.gyroData.z;
newVector = sqrt(newVector);
// Serial.print("New Len: "); Serial.println(newVector);
// are we moving
if (abs(newVector - storedVector) > MOVE_THRESHOLD) {
// Serial.println("Twinkle!");
strip.setBrightness(100);
flashRandom(1, 3); // first number is 'wait' delay, shorter num == shorter twinkle
flashRandom(5, 7); // second number is how many neopixels to simultaneously light up
flashRandom(3, 12);
} else {
strip.setBrightness(30);
colorWipe(strip.Color(88, 11, 28), 175); // wine red
colorWipe(strip.Color(133, 39, 78), 200); // cherry
}
}
void flashRandom(int wait, uint8_t howmany) {
for(uint16_t i=0; i<howmany; i++) {
// pick a random favorite color!
int c = random(FAVCOLORS);
int red = myFavoriteColors[c][0];
int green = myFavoriteColors[c][1];
int blue = myFavoriteColors[c][2];
// get a random pixel from the list
int j = random(strip.numPixels());
//Serial.print("Lighting up "); Serial.println(j);
// now we will 'fade' it in 5 steps
for (int x=0; x < 5; x++) {
int r = red * (x+1); r /= 5;
int g = green * (x+1); g /= 5;
int b = blue * (x+1); b /= 5;
strip.setPixelColor(j, strip.Color(r, g, b));
strip.show();
delay(wait);
}
// & fade out in 5 steps
for (int x=5; x >= 0; x--) {
int r = red * x; r /= 5;
int g = green * x; g /= 5;
int b = blue * x; b /= 5;
strip.setPixelColor(j, strip.Color(r, g, b));
strip.show();
delay(wait);
}
}
}
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
}
}
void colorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
}
}
Arduino Code for Sound Sensor
#include <Adafruit_NeoPixel.h>
#include <Adafruit_DotStar.h>
#include <Adafruit_DotStar.h>
#define N_PIXELS 10 // Number of pixels you are using
#define MIC_PIN 9 // Microphone is attached to Trinket GPIO #2/Gemma D2 (A1)
#define LED_PIN 12 // NeoPixel LED strand is connected to GPIO #0 / D0
#define DC_OFFSET 0 // DC offset in mic signal - if unusure, leave 0
#define NOISE 100 // Noise/hum/interference in mic signal
#define SAMPLES 60 // Length of buffer for dynamic level adjustment
#define TOP (N_PIXELS +1) // Allow dot to go slightly off scale
byte
peak = 0, // Used for falling dot
dotCount = 0, // Frame counter for delaying dot-falling speed
volCount = 0; // Frame counter for storing past volume data
int
vol[SAMPLES], // Collection of prior volume samples
lvl = 10, // Current "dampened" audio level
minLvlAvg = 0, // For dynamic adjustment of graph low & high
maxLvlAvg = 512;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
void setup() {
memset(vol, 0, sizeof(vol));
strip.begin();
}
void loop() {
uint8_t i;
uint16_t minLvl, maxLvl;
int n, height;
n = analogRead(MIC_PIN); // Raw reading from mic
n = abs(n - 512 - DC_OFFSET); // Center on zero
n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum
lvl = ((lvl * 7) + n) >> 3; // "Dampened" reading (else looks twitchy)
// Calculate bar height based on dynamic min/max levels (fixed point):
height = TOP * (lvl - minLvlAvg) / (long)(maxLvlAvg - minLvlAvg);
if(height < 0L) height = 0; // Clip output
else if(height > TOP) height = TOP;
if(height > peak) peak = height; // Keep 'peak' dot at top
// Color pixels based on rainbow gradient
for(i=0; i<N_PIXELS; i++) {
if(i >= height)
strip.setPixelColor(i, 0, 0, 0);
else
strip.setPixelColor(i,Wheel(map(i,0,strip.numPixels()-1,30,150)));
}
strip.show(); // Update strip
vol[volCount] = n; // Save sample for dynamic leveling
if(++volCount >= SAMPLES) volCount = 0; // Advance/rollover sample counter
// Get volume range of prior frames
minLvl = maxLvl = vol[0];
for(i=1; i<SAMPLES; i++) {
if(vol[i] < minLvl) minLvl = vol[i];
else if(vol[i] > maxLvl) maxLvl = vol[i];
}
// minLvl and maxLvl indicate the volume range over prior frames, used
// for vertically scaling the output graph (so it looks interesting
// regardless of volume level). If they're too close together though
// (e.g. at very low volume levels) the graph becomes super coarse
// and 'jumpy'...so keep some minimum distance between them (this
// also lets the graph go to zero when no sound is playing):
if((maxLvl - minLvl) < TOP) maxLvl = minLvl + TOP;
minLvlAvg = (minLvlAvg * 63 + minLvl) >> 6; // Dampen min/max levels
maxLvlAvg = (maxLvlAvg * 63 + maxLvl) >> 6; // (fake rolling average)
}
// Input a value 0 to 255 to get a color value.
// The colors are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
if(WheelPos < 75) {
return strip.Color(98, 20, 80);
} else if(WheelPos < 170) {
WheelPos -= 75;
return strip.Color(100, 149, 237);
} else {
WheelPos -= 170;
return strip.Color(0, 117, 200);
}
}
Reference List
Adams, M.L. (2007). The manly history of a ‘girls' sport’: Gender, class and the development of nineteenth-century figure skating. The International Journal of the History of Sport, 24(7), 872-893. doi:61:10.1111/ehr.2008.61
Reference
https://learn.adafruit.com/adafruit-lsm9ds0-accelerometer-gyro-magnetometer-9-dof-breakouts/assembly