LED Dress – Part 2: Virtual Prototype

December 11, 2013 Experiments

So, I’m still waiting for my group member to finish sewing the dress.  However, I really needed to start writing code for controlling the LEDs or I won’t finish in time.  So, I had the idea of using the Flora controller to take input data from the microphone, but instead of mocking up new LEDs, use shape objects in a Processing sketch.  After some researching, I found some code for the “Handshake” method of serial communication, and implemented it to help design the light patterns.  The prototype helped me code the light colors for the LEDs and put them in an array that I’ll be able to use later once I connect up the real LEDs.  Here is the simple circuit attached to the FLORA:

A_DSC_0255

The system takes sound input from the microphone, measuring the volume of the surroundings.  Since my neighbors would not appreciate me blasting my speakers to a level that would activate the microphone, I simulate a high volume environment with closely positioned music from my phone:

A_DSC_0246

The FLORA is doing all of the processing to change the values of an array, and passing them via serial communication to the Processing sketch:

Prototype_Base01

Color changes are triggered by the volume picked up by the microphone.  A moving average of 10 samples is taken of the smoothed microphone data.  After every 10 first level samples, a second level moving average is taken.  And after every 10 first level samples, a third level moving average is taken of the second level.  This allows me to have multiple averaged values without creating a giant array of 1,000 values to average.  I use these numbers to control the colors of the display, where the base color fades from red to yellow as the volume of the third level moving average increases.  The first level moving average is used to control the color of the “explosions” at volume spikes triggered by high values in the raw data.  I have also implemented some randomized color changes to produce a shimmering effect that are triggered by the amplitude of the raw data.  This picture shows an “explosion”(green) at a volume peak when the base volume is relatively low (red):

Prototype_Base03

And this shows an “explosion” (purple) when the base volume level is high (yellow/green):

Prototype_Base04

When no sound is picked up by the microphone and all of the moving averages have zeroed, the display fades to black, but will light up again as soon as a sound is picked up:

Prototype_Off

Here is a quick video showing the prototype in action:

Below is the Arduino code for for this experiment:

/* This sketch was used to develope the light pattern for the Reactive LED Dress by sending
   the LED color values to Processing to "light up" a mock up dress. This was in part to 
   explore using Serial communication, but also to demonstrate the power of using Processing
   as a test bed for Arduino projects when you may not have all of the parts or components,
   but need to start writing code.
*/
#include <Adafruit_NeoPixel.h>
// Microphone inputs
const int micPin = 9;
const int dcOff = 0;
const int noise = 10;

// 4 moving average values that are used to control the light levels
int lvlRaw = 0, lvlSm=0, lvlMd=0, lvlLg=0;

// pseudo-moving average serie
const int samples = 10;
// sampleSm stores averages of lvlRaw from the microphone
int sampleSm[samples],
// sampleMd stores averages lvlSm
sampleMd[samples],
// sampleLg stores averages lvlMd
sampleLg[samples];

// keeps track of when averages are taken
int countSm=0,countMd=0;

// array for channel colors of LEDs, this array is used to send to the Processing sketch for
// development, but will also be used to write to the LED displays
int ledDisp[16][3];

// color numbers for the different display elements
int baseColor[3];
int expColor[3];

int inByte = 0;

// sample taking timer
unsigned long timer=0;
int colorDelay = 300;

// explosion timer
unsigned long expTimer;

void setup(){
  //initialize serial
  Serial.begin(9600);
  establishContact();

  // initialize arrays
  memset(sampleSm, 0, sizeof(sampleSm));
  memset(sampleMd, 0, sizeof(sampleMd));
  memset(sampleLg, 0, sizeof(sampleLg));

  memset(baseColor, 0, sizeof(baseColor));
  memset(expColor, 0, sizeof(expColor)); 

  for (int i=0;i<16;i++){
    for (int j=0;j<3;j++){
      ledDisp[i][j]=0;
    }
  }

  //initialize timers
  timer=millis();
  expTimer = millis();
}

void loop(){
  uint8_t  i;
  uint16_t minLvl, maxLvl;
  int      n, height;

  // reads mic and dampens the input... taken from Adafruit AmpliTie sketch
  n   = analogRead(micPin);                        // Raw reading from mic 
  n   = abs(n - 512 - dcOff); // Center on zero
  n   = (n <= noise) ? 0 : (n - noise);             // Remove noise/hum
  lvlRaw = ((lvlRaw * 7) + n) >> 3;    // "Dampened" reading (else looks twitchy)

  // cycle values through sampleSm array, adding newst value to the front
  for (i=samples-1;i>0;i--){
    sampleSm[i]=sampleSm[i-1];
  }
  sampleSm[0] = lvlRaw;
  // update lvlSm 
  lvlSm = getAverage(sampleSm);
  // increment countSm
  countSm++;

  // every (sample #) sampleSm additions, the sampleMd array is augmented
  if (countSm>samples){
    //reset counter
    countSm = 0;
    // cycle values through sampleMd array, adding newst value to the front
    for (i=samples-1;i>0;i--){
      sampleMd[i]=sampleMd[i-1];
    }
    sampleMd[0] = lvlSm;
    //increment counter
    countMd++;
  }
  // update lvlMd number
  lvlMd = getAverage(sampleMd);

  // every (sample #) sampleMd additions, the sampleLg array is augmented
  if (countMd>samples){
    //reset counter
    countMd = 0;
    // cycle values through sampleLg array, adding newst value to the front
    for (i=samples-1;i>0;i--){
      sampleLg[i]=sampleLg[i-1];
    }
    sampleLg[0] = lvlMd;
    // no counter needed
  }
  // update lvlLg number
  lvlLg = getAverage(sampleLg);

  //update colors
  updateBaseColor();
  updateExpColor();
  // initiate explosions
  explosions();

  //send data to Processing
  sendSerial();
}

void sendSerial(){
  // "Handshake" communication, only sends when Processing is ready
  if(Serial.available()>0)
  { 
    //reads to see if Processing is ready
    inByte = Serial.read();

    // sends the array values with commas inbetween
    Serial.print(ledDisp[0][0]);
    Serial.print(",");
    Serial.print(ledDisp[0][1]);
    Serial.print(",");
    Serial.print(ledDisp[0][2]);

    for (int i = 1;i<16;i++){
      for (int j = 0;j<3;j++){
        Serial.print(",");
        Serial.print(ledDisp[i][j]);
      }
    }
    Serial.println();
    //delay(100);
  }

}

// calculates and returns average of the array passed into it
int getAverage(int inArray[]){
  int calc=0;
  for(int i=0;i<sizeof(inArray);i++)
    calc+=inArray[i];

  calc/=sizeof(inArray);
  return calc;
}

void establishContact()
{
  // starts the "Handshake"
  while (Serial.available() <= 0)  {
    Serial.println("400,300");   // send an initial string
    delay(300);
  }
}

// random pattern used for testing
void randColors(){
  if (timer+colorDelay<millis()){
    for (int i = 0; i < 16; i++) {  
      ledDisp[i][0]+=lvlRaw;
      ledDisp[i][1]+=lvlSm;
      ledDisp[i][2]+=lvlMd;
      for (int j=0;j<3;j++){
        if (ledDisp[i][j]>255)
          ledDisp[i][j]-=256;
      }
    }
    timer=millis();
  }
}

int lastExplosion;
int stepsSinceExp;
int expDelay=50;
int expTol = 40;

void explosions(){
  int flicker;

  if (expTimer+expDelay<millis()){
    int change = 15;
    //constrains input to randomizing code so nothing goes out of bounds
    flicker = constrain(lvlRaw,0,50);
    for (int i = 0; i < 16; i++) {  
      // adds random integer from -change to change allowing full display to be reactive to sound
      ledDisp[i][0]=baseColor[0]+int(map(random(0,flicker),0,flicker,-change,change));
      ledDisp[i][1]=baseColor[1]+int(map(random(0,flicker),0,flicker,-change,change));
      ledDisp[i][2]=baseColor[2]+int(map(random(0,flicker),0,flicker,-change,change));
      //      ledDisp[i][0]=baseColor[0];
      //      ledDisp[i][1]=baseColor[1];
      //      ledDisp[i][2]=baseColor[2];
    }
    if (stepsSinceExp>8&&abs(lvlSm-lvlLg)>expTol){
      // sets random explosion point between the 2nd and 7th LEDs
      lastExplosion = int(random(1,6));
      stepsSinceExp=0;
    }

    if (stepsSinceExp==0){
      // when stepsSinceExp is 0, the explosion just happened, so only two LEDs need to light up
      ledDisp[lastExplosion][0]=expColor[0];
      ledDisp[lastExplosion][1]=expColor[1];
      ledDisp[lastExplosion][2]=expColor[2];
      ledDisp[7+8-lastExplosion][0]=expColor[0];
      ledDisp[7+8-lastExplosion][1]=expColor[1];
      ledDisp[7+8-lastExplosion][2]=expColor[2];
    }
    else{
      //position is calculated for the positions above and below the explosion point
      //based on steps since explosion
      int above = lastExplosion-stepsSinceExp;
      int below = lastExplosion+stepsSinceExp;
      if(above>=0){
        // keep display from going out of upper bound
        ledDisp[above][0]=expColor[0];
        ledDisp[above][1]=expColor[1];
        ledDisp[above][2]=expColor[2];
        ledDisp[7+8-above][0]=expColor[0];
        ledDisp[7+8-above][1]=expColor[1];
        ledDisp[7+8-above][2]=expColor[2];
      }
       // keep display from going out of lower bound
      if(below<=7){
        ledDisp[below][0]=expColor[0];
        ledDisp[below][1]=expColor[1];
        ledDisp[below][2]=expColor[2];
        ledDisp[7+8-below][0]=expColor[0];
        ledDisp[7+8-below][1]=expColor[1];
        ledDisp[7+8-below][2]=expColor[2];
      }

    }

    // increases steps since explosion and punches time
    stepsSinceExp++;
    expTimer = millis();
  }
}

void updateBaseColor(){

  if (lvlSm+lvlMd+lvlLg==0){
    // fades out color if there has been no sound for a while no sound
    for (int i=0;i<3;i++){ 
    if (baseColor[i] >0)
     baseColor[i] -= 1;
     else
     baseColor[i] = 0;
    }

  }
  else{
    //base color is based on Long running average, fades from red to yellow
    baseColor[0] = int(map(constrain(lvlLg,0,80),0,80,255,150));
    baseColor[1] = int(map(constrain(lvlLg,0,80),0,80,0,150));
    baseColor[2] = 0;
  }
}

void updateExpColor(){
  // Explosion color is based on the Raw data output
  expColor[0] = int(map(constrain(lvlRaw,0,80),0,80,100,50));
  expColor[1] = int(map(constrain(lvlRaw,0,80),0,80,50,100));
  expColor[2] = int(map(constrain(lvlRaw,0,80),0,80,180,60));
}

And here is the Processing code:

/*  Processing protoype for the Reactive LED Dress:
 This sketch accepts the RGB values for the LEDs and draws them as
 ellipses to do the initial prototyping for the light patterns before
 I had the actual dress back from my group members
 */

import processing.serial.*;
//imports background dress image
PImage bg;

Serial port;

// Array of LED values with {X position, Y Position, R val, G val, B val}
int ledDisp[][] = new int[16][5];

void setup()
{
  // sets up background image
  size(600, 600);
  bg = loadImage("DressDemo.jpg");

  //intializes serial comunication
  println(Serial.list());
  port = new Serial(this, Serial.list()[0], 9600);
  port.bufferUntil('\n');

  //initializes led Array
  for (int i=0;i<16;i++) {
    for (int j=0;j<5;j++) {
      ledDisp[i][j]=0;
    }
  }

  // writes positions of "LEDs"
  ledDisp[0][0]=5+width/2-30;
  ledDisp[0][1]=80;
  ledDisp[1][0]=5+width/2-15;
  ledDisp[1][1]=100;
  ledDisp[2][0]=5+width/2-12;
  ledDisp[2][1]=135;
  ledDisp[3][0]=5+width/2-10;
  ledDisp[3][1]=180;
  ledDisp[4][0]=5+width/2-10;
  ledDisp[4][1]=230;
  ledDisp[5][0]=5+width/2-20;
  ledDisp[5][1]=280;
  ledDisp[6][0]=5+width/2-30;
  ledDisp[6][1]=350;
  ledDisp[7][0]=5+width/2-70;
  ledDisp[7][1]=450;
  ledDisp[8][0]=5+width/2+70;
  ledDisp[8][1]=450;
  ledDisp[9][0]=5+width/2+30;
  ledDisp[9][1]=350;
  ledDisp[10][0]=5+width/2+20;
  ledDisp[10][1]=280;
  ledDisp[11][0]=5+width/2+10;
  ledDisp[11][1]=230;
  ledDisp[12][0]=5+width/2+10;
  ledDisp[12][1]=180;
  ledDisp[13][0]=5+width/2+12;
  ledDisp[13][1]=135;
  ledDisp[14][0]=5+width/2+15;
  ledDisp[14][1]=100;
  ledDisp[15][0]=5+width/2+30;
  ledDisp[15][1]=80;
  smooth();
  noStroke();
}

void draw()
{
  // clears old data, shows background image
  background(bg);

  // draws LEDs as ellipses
  for (int i=0;i<16;i++) {
    fill(ledDisp[i][2], ledDisp[i][3], ledDisp[i][4]);
    ellipse(ledDisp[i][0], ledDisp[i][1], 18, 18);
  }
}

void serialEvent(Serial port)
{
  // Serial Handshake, reads incoming string
  String inString = port.readString();

  // trims string
  inString = trim(inString);

  if (inString != null)
  {
    inString = trim(inString);

    // parse string into values between commas
    int[] pos = int(split(inString, ","));

    // write values to the ledDisp array
    for (int i = 0; i < pos.length/3; i++) {  
      for (int j = 0; j < 3; j++) {  
        ledDisp[i][j+2]=pos[i*3+j];
        //print(pos[posNum] + ",");
      } 
      //print(pos[posNum] + ",");
    }       
    //println();
  }
  // close "Handshake" signalling for new data 
  port.write("A");
}

Leave a Reply

Your email address will not be published. Required fields are marked *