Laptop Light Show – Final Project

Laptop Light Show:


For this project I wanted to remain with using light like I have for the semester. All semester I had been interested in creating a light show that was responsive to sound that would be mounted on the back of a television, laptop, or desktop monitor. To make my idea a little more original I had to do a lot of research on how I would do this. When we had to watch Casey Reas I was interested in the Processing application he had mentioned so I did a little more research on it. I came across a code that was able to take the Arduino code that I was using for my RGB LED strips, and then make it 1000x cooler. 

The Processing code that I found does a few things. The first part of the code sets a value for 256 tonal values so that pretty much any color can be displayed on the RGB LEDs. This helps in creating a smooth transition between colors, at times making an ambient effect with the lights, and at other times creating fast paced flickering. The other part of the code was a little more sophisticated. This program runs while I am displaying whatever it is I am displaying on my monitor and isn’t uploaded to the Arduino like the other ones, instead it runs directly from my computer and through the Arduino. This code divides a computer screen into however many segments you decide (In my case 25) and averages the RGB values and gives that segment its own RGB value. This value is then displayed on the designated LED. The program is constantly scanning the computer so there is no delay between what is displayed on the screen and what is displayed on the LEDs. This allows for a super accurate light show to be produced based off of what is being displayed on my screen.

I tried to find out some more about Processing but there isn’t a lot of information about all of its capabilities. Here is how the website describes it:

“Processing is an open source programming language and environment for people who want to create images, animations, and interactions. Initially developed to serve as a software sketchbook and to teach fundamentals of computer programming within a visual context, Processing also has evolved into a tool for generating finished professional work. Today, there are tens of thousands of students, artists, designers, researchers, and hobbyists who use Processing for learning, prototyping, and production.”

It is basically using programming language builds on the Java language, but uses a simplified syntax and graphics programming model. The interface of the program is almost identical to the Arduino’s interface.

I decided to mount my project on a foam board for right now. I originally did this just to keep all the LEDs stable and test out my project. I really liked how it turned out and haven’t really decided where I want to commit to place it long term. The foam board is about 30″ diagonal, so you can imagine this on the back of a TV or large monitor. The lights, depending on the effect that you want, should be from 1″- 6″ from a wall to allow for the light to be reflected and diffused on a wall.The Arduino is there in the center and wires are connected to pins 11, 13, and GRD that connect into the RGB LEDs. The RGB LEDs are powered by a 5V DC external power supply. 

Other Pictures:

 

Video: (Watch the full thing it is pretty sweet)

Code:


Arduino-

#include <SPI.h>

#define LED_DDR  DDRB
#define LED_PORT PORTB
#define LED_PIN  _BV(PORTB5)

static const uint8_t magic[] = {‘A’,’d’,’a’};
#define MAGICSIZE  sizeof(magic)
#define HEADERSIZE (MAGICSIZE + 3)

#define MODE_HEADER 0
#define MODE_HOLD   1
#define MODE_DATA   2

static const unsigned long serialTimeout = 15000; 

void setup()
{

  uint8_t
    buffer[256],
    indexIn       = 0,
    indexOut      = 0,
    mode          = MODE_HEADER,
    hi, lo, chk, i, spiFlag;
  int16_t
    bytesBuffered = 0,
    hold          = 0,
    c;
  int32_t
    bytesRemaining;
  unsigned long
    startTime,
    lastByteTime,
    lastAckTime,
    t;

  LED_DDR  |=  LED_PIN; 
  LED_PORT &= ~LED_PIN; 

  Serial.begin(115200); 

  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV16); 

  uint8_t testcolor[] = { 0, 0, 0, 255, 0, 0 };
  for(char n=3; n>=0; n–) {
    for(c=0; c<25000; c++) {
      for(i=0; i<3; i++) {
        for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); );
      }
    }
    delay(1); 
  }

  Serial.print(“Adan”); 

  startTime    = micros();
  lastByteTime = lastAckTime = millis();

  for(;;) {

    t = millis();
    if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) {
      buffer[indexIn++] = c;
      bytesBuffered++;
      lastByteTime = lastAckTime = t; 
    } else {
   
      if((t – lastAckTime) > 1000) {
        Serial.print(“Adan”); 
        lastAckTime = t; 
      }
      
      if((t – lastByteTime) > serialTimeout) {
        for(c=0; c<32767; c++) {
          for(SPDR=0; !(SPSR & _BV(SPIF)); );
        }
        delay(1); 
        lastByteTime = t; 
      }
    }

    switch(mode) {

     case MODE_HEADER:

  
      if(bytesBuffered >= HEADERSIZE) {

        for(i=0; (i<MAGICSIZE) && (buffer[indexOut++] == magic[i++]););
        if(i == MAGICSIZE) {
         
          hi  = buffer[indexOut++];
          lo  = buffer[indexOut++];
          chk = buffer[indexOut++];
          if(chk == (hi ^ lo ^ 0x55)) {
           
            bytesRemaining = 3L * (256L * (long)hi + (long)lo + 1L);
            bytesBuffered -= 3;
            spiFlag        = 0;         
            mode           = MODE_HOLD; 
          } else {
           
            indexOut  -= 3;
          }
        } 
        bytesBuffered -= i;
      }
      break;

     case MODE_HOLD:

      if((micros() – startTime) < hold) break; 
      LED_PORT &= ~LED_PIN;  
      mode      = MODE_DATA; 

     case MODE_DATA:

      while(spiFlag && !(SPSR & _BV(SPIF))); 
      if(bytesRemaining > 0) {
        if(bytesBuffered > 0) {
          SPDR = buffer[indexOut++]; 
          bytesBuffered–;
          bytesRemaining–;
          spiFlag = 1;
        }
       
        if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) {
          startTime = micros();
          hold      = 100 + (32 – bytesBuffered) * 10;
          mode      = MODE_HOLD;
}
      } else {
       
        startTime  = micros();
        hold       = 1000;        
        LED_PORT  |= LED_PIN;     
        mode       = MODE_HEADER; 
      }
    } 
  } 
}

void loop()
{
}
—————————————————————————————————————————————————————————————————————–

Processing (pt 1)-

import processing.serial.*;

int N_LEDS = 25;

void setup()
{
  byte[] buffer = new byte[6 + N_LEDS * 3];
  Serial myPort;
  int    i, hue1, hue2, bright, lo, r, g, b, t, prev, frame = 0;
  long   totalBytesSent = 0;
  float  sine1, sine2;

  noLoop();

  // Assumes the Arduino is the first/only serial device.  If this is not the
  // case, change the device index here.  println(Serial.list()); can be used
  // to get a list of available serial devices.
  myPort = new Serial(this, Serial.list()[0], 115200);

  // A special header / magic word is expected by the corresponding LED
  // streaming code running on the Arduino.  This only needs to be initialized
  // once because the number of LEDs remains constant:
  buffer[0] = ‘A’;                                // Magic word
  buffer[1] = ‘d’;
  buffer[2] = ‘a’;
  buffer[3] = byte((N_LEDS – 1) >> 8);            // LED count high byte
  buffer[4] = byte((N_LEDS – 1) & 0xff);          // LED count low byte
  buffer[5] = byte(buffer[3] ^ buffer[4] ^ 0x55); // Checksum

  sine1 = 0.0;
  hue1  = 0;
  prev  = second(); // For bandwidth statistics

  for (;;) {
    sine2 = sine1;
    hue2  = hue1;

    // Start at position 6, after the LED header/magic word
    for (i = 6; i < buffer.length; ) {
      // Fixed-point hue-to-RGB conversion.  ‘hue2’ is an integer in the
      // range of 0 to 1535, where 0 = red, 256 = yellow, 512 = green, etc.
      // The high byte (0-5) corresponds to the sextant within the color
      // wheel, while the low byte (0-255) is the fractional part between
      // the primary/secondary colors.
      lo = hue2 & 255;
      switch((hue2 >> 8) % 6) {
      case 0:
        r = 255;
        g = lo;
        b = 0;
        break;
      case 1:
        r = 255 – lo;
        g = 255;
        b = 0;
        break;
      case 2:
        r = 0;
        g = 255;
        b = lo;
        break;
      case 3:
        r = 0;
        g = 255 – lo;
        b = 255;
        break;
      case 4:
        r = lo;
        g = 0;
        b = 255;
        break;
      default:
        r = 255;
        g = 0;
        b = 255 – lo;
        break;
      }

      // Resulting hue is multiplied by brightness in the range of 0 to 255
      // (0 = off, 255 = brightest).  Gamma corrrection (the ‘pow’ function
      // here) adjusts the brightness to be more perceptually linear.
      bright      = int(pow(0.5 + sin(sine2) * 0.5, 2.8) * 255.0);
      buffer[i++] = byte((r * bright) / 255);
      buffer[i++] = byte((g * bright) / 255);
      buffer[i++] = byte((b * bright) / 255);

      // Each pixel is slightly offset in both hue and brightness
      hue2  += 40;
      sine2 += 0.3;
    }

    // Slowly rotate hue and brightness in opposite directions
    hue1   = (hue1 + 4) % 1536;
    sine1 -= .03;

    // Issue color data to LEDs and keep track of the byte and frame counts
    myPort.write(buffer);
    totalBytesSent += buffer.length;
    frame++;

    // Update statistics once per second
    if ((t = second()) != prev) {
      print(“Average frames/sec: “);
      print(int((float)frame / (float)millis() * 1000.0));
      print(“, bytes/sec: “);
      println(int((float)totalBytesSent / (float)millis() * 1000.0));
      prev = t;
    }
  }
}

void draw()
{
}

——————————————————————————————————————————

Processing (pt 2)-
import java.awt.*;
import java.awt.image.*;
import processing.serial.*;
static final short minBrightness = 120;
static final short fade = 75;
// Pixel size for the live preview image.
static final int pixelSize = 20;
static final boolean useFullScreenCaps = true;
static final int timeout = 5000; // 5 seconds
static final int displays[][] = new int[][] {
   {0,9,6} 
};
static final int leds[][] = new int[][] {
  {0,3,5}, {0,2,5}, {0,1,5}, {0,0,5}, // Bottom edge, left half
  {0,0,4}, {0,0,3}, {0,0,2}, {0,0,1}, // Left edge
  {0,0,0}, {0,1,0}, {0,2,0}, {0,3,0}, {0,4,0}, // Top edge
           {0,5,0}, {0,6,0}, {0,7,0}, {0,8,0}, // More top edge
  {0,8,1}, {0,8,2}, {0,8,3}, {0,8,4}, // Right edge
  {0,8,5}, {0,7,5}, {0,6,5}, {0,5,5}  // Bottom edge, right half
};
byte[]           serialData  = new byte[6 + leds.length * 3];
short[][]        ledColor    = new short[leds.length][3],
                 prevColor   = new short[leds.length][3];
byte[][]         gamma       = new byte[256][3];
int              nDisplays   = displays.length;
Robot[]          bot         = new Robot[displays.length];
Rectangle[]      dispBounds  = new Rectangle[displays.length],
                 ledBounds;  // Alloc’d only if per-LED captures
int[][]          pixelOffset = new int[leds.length][256],
                 screenData; // Alloc’d only if full-screen captures
PImage[]         preview     = new PImage[displays.length];
Serial           port;
DisposeHandler   dh; // For disabling LEDs on exit
// INITIALIZATION ————————————————————
void setup() {
  GraphicsEnvironment     ge;
  GraphicsConfiguration[] gc;
  GraphicsDevice[]        gd;
  int                     d, i, totalWidth, maxHeight, row, col, rowOffset;
  int[]                   x = new int[16], y = new int[16];
  float                   f, range, step, start;
  dh = new DisposeHandler(this); // Init DisposeHandler ASAP
  port = new Serial(this, Serial.list()[0], 115200);
  dispBounds = new Rectangle[displays.length];
  if(useFullScreenCaps == true) {
    screenData = new int[displays.length][];
    // ledBounds[] not used
  } else {
    ledBounds  = new Rectangle[leds.length];
    // screenData[][] not used
  }
  ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  gd = ge.getScreenDevices();
  if(nDisplays > gd.length) nDisplays = gd.length;
  totalWidth = maxHeight = 0;
  for(d=0; d<nDisplays; d++) { // For each display…
    try {
      bot[d] = new Robot(gd[displays[d][0]]);
    }
    catch(AWTException e) {
      System.out.println(“new Robot() failed”);
      continue;
    }
    gc              = gd[displays[d][0]].getConfigurations();
    dispBounds[d]   = gc[0].getBounds();
    dispBounds[d].x = dispBounds[d].y = 0;
    preview[d]      = createImage(displays[d][1], displays[d][2], RGB);
    preview[d].loadPixels();
    totalWidth     += displays[d][1];
    if(d > 0) totalWidth++;
    if(displays[d][2] > maxHeight) maxHeight = displays[d][2];
  }
  for(i=0; i<leds.length; i++) { // For each LED…
    d = leds[i][0]; // Corresponding display index
    // Precompute columns, rows of each sampled point for this LED
    range = (float)dispBounds[d].width / (float)displays[d][1];
    step  = range / 16.0;
    start = range * (float)leds[i][1] + step * 0.5;
    for(col=0; col<16; col++) x[col] = (int)(start + step * (float)col);
    range = (float)dispBounds[d].height / (float)displays[d][2];
    step  = range / 16.0;
    start = range * (float)leds[i][2] + step * 0.5;
    for(row=0; row<16; row++) y[row] = (int)(start + step * (float)row);
    if(useFullScreenCaps == true) {
      // Get offset to each pixel within full screen capture
      for(row=0; row<16; row++) {
        for(col=0; col<16; col++) {
          pixelOffset[i][row * 16 + col] =
            y[row] * dispBounds[d].width + x[col];
        }
      }
    } else {
      // Calc min bounding rect for LED, get offset to each pixel within
      ledBounds[i] = new Rectangle(x[0], y[0], x[15]-x[0]+1, y[15]-y[0]+1);
      for(row=0; row<16; row++) {
        for(col=0; col<16; col++) {
          pixelOffset[i][row * 16 + col] =
            (y[row] – y[0]) * ledBounds[i].width + x[col] – x[0];
        }
      }
    }
  }
  for(i=0; i<prevColor.length; i++) {
    prevColor[i][0] = prevColor[i][1] = prevColor[i][2] =
      minBrightness / 3;
  }
  // Preview window shows all screens side-by-side
  size(totalWidth * pixelSize, maxHeight * pixelSize, JAVA2D);
  noSmooth();
  serialData[0] = ‘A’;                              // Magic word
  serialData[1] = ‘d’;
  serialData[2] = ‘a’;
  serialData[3] = (byte)((leds.length – 1) >> 8);   // LED count high byte
  serialData[4] = (byte)((leds.length – 1) & 0xff); // LED count low byte
  serialData[5] = (byte)(serialData[3] ^ serialData[4] ^ 0x55); // Checksum
  // Pre-compute gamma correction table for LED brightness levels:
  for(i=0; i<256; i++) {
    f           = pow((float)i / 255.0, 2.8);
    gamma[i][0] = (byte)(f * 255.0);
    gamma[i][1] = (byte)(f * 240.0);
    gamma[i][2] = (byte)(f * 220.0);
  }
}
Serial openPort() {
  String[] ports;
  String   ack;
  int      i, start;
  Serial   s;
  ports = Serial.list(); // List of all serial ports/devices on system.
  for(i=0; i<ports.length; i++) { // For each serial port…
    System.out.format(“Trying serial port %sn”,ports[i]);
    try {
      s = new Serial(this, ports[i], 115200);
    }
    catch(Exception e) {
      // Can’t open port, probably in use by other software.
      continue;
    }
    // Port open…watch for acknowledgement string…
    start = millis();
    while((millis() – start) < timeout) {
      if((s.available() >= 4) &&
        ((ack = s.readString()) != null) &&
        ack.contains(“Adan”)) {
          return s; // Got it!
      }
    }
    // Connection timed out.  Close port and move on to the next.
    s.stop();
  }
  return new Serial(this, ports[0], 115200);
}
void draw () {
  BufferedImage img;
  int           d, i, j, o, c, weight, rb, g, sum, deficit, s2;
  int[]         pxls, offs;
  if(useFullScreenCaps == true ) {
    // Capture each screen in the displays array.
    for(d=0; d<nDisplays; d++) {
      img = bot[d].createScreenCapture(dispBounds[d]);
      // Get location of source pixel data
      screenData[d] =
        ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
    }
  }
  weight = 257 – fade; // ‘Weighting factor’ for new frame vs. old
  j      = 6;          // Serial led data follows header / magic word
  for(i=0; i<leds.length; i++) {  // For each LED…
    d = leds[i][0]; // Corresponding display index
    if(useFullScreenCaps == true) {
      // Get location of source data from prior full-screen capture:
      pxls = screenData[d];
    } else {
      // Capture section of screen (LED bounds rect) and locate data::
      img  = bot[d].createScreenCapture(ledBounds[i]);
      pxls = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
    }
    offs = pixelOffset[i];
    rb = g = 0;
    for(o=0; o<256; o++) {
      c   = pxls[offs[o]];
      rb += c & 0x00ff00ff; // Bit trickery: R+B can accumulate in one var
      g  += c & 0x0000ff00;
    }
    // Blend new pixel value with the value from the prior frame
    ledColor[i][0]  = (short)((((rb >> 24) & 0xff) * weight +
                               prevColor[i][0]     * fade) >> 8);
    ledColor[i][1]  = (short)(((( g >> 16) & 0xff) * weight +
                               prevColor[i][1]     * fade) >> 8);
    ledColor[i][2]  = (short)((((rb >>  8) & 0xff) * weight +
                               prevColor[i][2]     * fade) >> 8);
    // Boost pixels that fall below the minimum brightness
    sum = ledColor[i][0] + ledColor[i][1] + ledColor[i][2];
    if(sum < minBrightness) {
      if(sum == 0) { // To avoid divide-by-zero
        deficit = minBrightness / 3; // Spread equally to R,G,B
        ledColor[i][0] += deficit;
        ledColor[i][1] += deficit;
        ledColor[i][2] += deficit;
      } else {
        deficit = minBrightness – sum;
        s2      = sum * 2;
        ledColor[i][0] += deficit * (sum – ledColor[i][0]) / s2;
        ledColor[i][1] += deficit * (sum – ledColor[i][1]) / s2;
        ledColor[i][2] += deficit * (sum – ledColor[i][2]) / s2;
      }
    }
    serialData[j++] = gamma[ledColor[i][0]][0];
    serialData[j++] = gamma[ledColor[i][1]][1];
    serialData[j++] = gamma[ledColor[i][2]][2];
    preview[d].pixels[leds[i][2] * displays[d][1] + leds[i][1]] =
     (ledColor[i][0] << 16) | (ledColor[i][1] << 8) | ledColor[i][2];
  }
  if(port != null) port.write(serialData); // Issue data to Arduino
  // Show live preview image(s)
  scale(pixelSize);
  for(i=d=0; d<nDisplays; d++) {
    preview[d].updatePixels();
    image(preview[d], i, 0);
    i += displays[d][1] + 1;
  }
  println(frameRate); // How are we doing?
  // Copy LED color data to prior frame array for next pass
  arraycopy(ledColor, 0, prevColor, 0, ledColor.length);
}
public class DisposeHandler {
  DisposeHandler(PApplet pa) {
    pa.registerDispose(this);
  }
  public void dispose() {
    java.util.Arrays.fill(serialData, 6, serialData.length, (byte)0);
    if(port != null) port.write(serialData);
  }
}