Using Video Projectors as Stage Light

We installed three video projectors facing the audience at a small venue, and made a patch in Processing simulating moving heads, strobes and other effects we came up with.

Click for my Processing source code with all its kludges and betaness.

I have divided the code in three files:

lightTable.pde  -  containing setup(), draw(), midi control and some general routines
fixture.pde – containing the class with my virtual fixture
dmx.pde – containing the  failed dmx receiving routines (some of which are still needed for the program to run)

lightTable.pde

import com.juanjo.openDmx.*;

import rwmidi.*;
MidiInput input;

Minim minim;
import ddf.minim.*;
AudioInput audioInn;

int nImg=1;
PImage[] img= new PImage[nImg];
int testDmxChannel=4;

  int shapeX;
  int shapeY;

ArrayList fixtures;
int dmx[] = new int[512];
boolean connected;
color[] palette= new color[16];

void setup() {
  img[0]=loadImage("gobo1.png");
  size(800, 600, P2D);
  frameRate(30);
  noCursor();
  minim = new Minim(this);
  audioInn = minim.getLineIn(Minim.STEREO, width);
  int ofs=16;
  dmx[0+ofs]=255; //int intensityOffs=0;
  dmx[1+ofs]=110; //int rotationOffs=1;
  dmx[2+ofs]=50; //int quantityOffs=2;
  dmx[3+ofs]=50; //int diameterOffs=3;
  dmx[4+ofs]=50; //int spreadOffs=4;
  dmx[5+ofs]=0; //int colorOffs=5;
  dmx[15+ofs]=2*10; //int effectOffs=15;

  dmx[15]=4*10;  

  palette[0]=color(255, 255, 255);
  palette[1]=color(255, 0, 0);
  palette[2]=color(0, 255, 0);
  palette[3]=color(0, 0, 255);
  palette[4]=color(255, 255, 0);
  palette[5]=color(255, 0, 255);
  palette[6]=color(0, 255, 255);
  palette[7]=color(255, 128, 128);
  palette[8]=color(128, 255, 128);
  palette[9]=color(128, 128, 255);
  palette[10]=color(255, 255, 128);
  palette[11]=color(255, 128, 255);
  palette[12]=color(128, 255, 255);
  palette[13]=color(255, 255, 255);
  palette[14]=color(255, 255, 255);
  palette[15]=color(255, 255, 255);
  connected=OpenDmx.connect(OpenDmx.OPENDMX_RX);
  if (!connected) {
    println("Open Dmx widget not detected!");
  }
  else {
    System.out.println("Open Dmx widget up running!");
  }

  // Create an empty ArrayList
  fixtures = new ArrayList();

  // Start by adding one element
  for (int i=0; i<nFixtures; i++) {
    fixtures.add(new Fixture(i*chPrFixture+dmxAdress));
    Fixture fixture = (Fixture) fixtures.get(i);
    fixture.initDott();
  }
  println(RWMidi.getInputDevices());
  input = RWMidi.getInputDevices()[8].createInput(this);  

  initDmxInfo();
  //initDmxStart();
  loadDmx();
}

void controllerChangeReceived(rwmidi.Controller cntrl){
 int cc=cntrl.getCC();
 int val=cntrl.getValue();
 //println("cc recieved:"+cc+":"+val);
 dmx[cc]=val*2;
}

void keyPressed() {
  if (key=='S') {
    saveDmx();
    println("DMX saved");
  }
  if (key=='r') {
    loadDmx();
    println("All DMX reset to last saved values");
  }
  if (key=='i') {
    initDmxStart();
  }
  if (key=='R') {
    for (int fn=0; fn<nFixtures; fn++) {
      for (int ch=0; ch<chPrFixture; ch++) {
        dmx[dmxAdress+chPrFixture*fn+ch]=int(random(256));
      }
      dmx[dmxAdress+chPrFixture*fn+15]=int(random(6)*10+10);
      dmx[dmxAdress+chPrFixture*fn]=255;
    }
  }
  if (keyCode==LEFT) {
    if (testDmxChannel>0) {
      testDmxChannel--;
      println(channelName[testDmxChannel] + ":" +dmx[testDmxChannel]);
    }
  }
  if (keyCode==RIGHT) {
    if (testDmxChannel<512) {
      testDmxChannel++;
      println(channelName[testDmxChannel] + ":" +dmx[testDmxChannel]);
    }
  }
  if (keyCode==UP) {
    if (dmx[testDmxChannel]<255) {
      dmx[testDmxChannel]++;
    }
  }
  if (keyCode==DOWN) {
    if (dmx[testDmxChannel]>0) {
      dmx[testDmxChannel]--;
    }
  }

}

void mousePressed() { //only for testing
}

void draw() {
  receiveDmx();
  printDmxInfo();
  background(0);

  for (int i = 0; i<nFixtures; i++) {
    Fixture fixture = (Fixture) fixtures.get(i);
    fixture.move();
  }
  //println(frameRate);
}

void imageOut(int n, int x, int y){
  pushMatrix();
  rotate(1);
  image(img[n],x-(img[n].width/2),y-(img[n].height/2));
  popMatrix();
}

void setChName(int i, String s){
  channelName[i]=channelName[i]+s;
}

int dmxOld[] = new int[512];
String channelName[] = new String[512];
void initDmxInfo() {
  for (int i=0; i<512; i++) {
    channelName[i]="Ch."+str(i+1)+" ";
  }
  for (int i=0; i<nFixtures; i++) {
    setChName(i*chPrFixture+dmxAdress+intensityOffs,"Intensity");
    setChName(i*chPrFixture+dmxAdress+speedOffs,"Speed");
    setChName(i*chPrFixture+dmxAdress+quantityOffs,"Quantity");
    setChName(i*chPrFixture+dmxAdress+diameterOffs,"Diameter");
    setChName(i*chPrFixture+dmxAdress+spreadOffs,"Spread");
    setChName(i*chPrFixture+dmxAdress+colorOffs,"Color");
    setChName(i*chPrFixture+dmxAdress+panOffs,"Pan");
    setChName(i*chPrFixture+dmxAdress+tiltOffs,"Tilt");
    setChName(i*chPrFixture+dmxAdress+effectOffs,"Effect");
  }
}

void printDmxInfo() {
  boolean change=false;
  for (int i=0; i<512; i++) {
    if (dmx[i]!=dmxOld[i]) {
      print(channelName[i]+':'+dmx[i]+"   ");
      change=true;
      dmxOld[i]=dmx[i];
    }
  }
  if (change) {
    println();
  }
}

color fade(color c, int fader) {
  return color(fader*red(c)/255, fader*green(c)/255, fader*blue(c)/255);
}

fixture.pde

int intensityOffs=0;
int speedOffs=1;
int panOffs=2;
int tiltOffs=3;
int spreadOffs=4;
int quantityOffs=5;
int diameterOffs=6;
int colorOffs=7;
int effectOffs=8;

int nFixtures=8;
int chPrFixture=12;
int dmxAdress=0;
int nEffects=8;

class Fixture {
  int dmxAddr;

  int intensity;
  float rotation;
  int quantity;
  int diametr;
  int spread;
  int speed;
  int colr;
  int pan;
  int tilt;
  int effect;

  Fixture(int da) {
    dmxAddr=da;
  }

  void move() {
    colr=dmx[dmxAddr+colorOffs]/8;
    pan=dmx[dmxAddr+panOffs]*width/255+shapeX;
    tilt=(255-dmx[dmxAddr+tiltOffs])*height/255+shapeY;
    intensity=dmx[dmxAddr+intensityOffs];

    effect=dmx[dmxAddr+effectOffs]*nEffects/255+1;
    if (effect==1) {gobo();}
    if (effect==2) {dott();}
    if (effect==3) {wall();}
    if (effect==4) {wave();}
    if (effect==5) {soundWave();}
    if (effect==6) {strobe();}
    if (effect==7) {shaper();}
    if (effect==8) {goboImage();}
  }

  ////////////////////////////////////////////////////////////////////////// GOBO EFFECT
  void gobo() {
    rotation=rotation+(128-dmx[dmxAddr+speedOffs])/800.0;
    quantity=dmx[dmxAddr+quantityOffs]/10;
    if (intensity>0)
      drawGobo();
  }

  void drawGobo() {
    diametr=dmx[dmxAddr+diameterOffs];
    spread=dmx[dmxAddr+spreadOffs];
    color c=fade(palette[colr%16], intensity);
    //  fill(c);
    if (colr>15) {fill(c);} else {noFill();}
    stroke(c);
    for (int i=0; i<quantity; i++) {
      int x=pan+int(spread*sin(i*2*PI/quantity+rotation));
      int y=tilt+int(spread*cos(i*2*PI/quantity+rotation));
      ellipse(x, y, diametr, diametr);
      ellipse(x+1, y, diametr, diametr);
      ellipse(x, y+1, diametr, diametr);
      ellipse(x+1, y+1, diametr, diametr);
    }
  }

  /////////////////////////////////////////////////////////////////////// DOTT EFFECT
  void dott(){
    quantity=dmx[dmxAddr+quantityOffs]/10;
    speed=dmx[dmxAddr+speedOffs];
    diametr=dmx[dmxAddr+diameterOffs];
    if (intensity>0) {drawDott();}
  }

  float dottX[]=new float[200];
  float dottY[]=new float[200];
  float dottXV[]=new float[200];
  float dottYV[]=new float[200];
  color dottC[]=new color[200];
  void initDott(){
    for (int i=0; i<199; i++){
      float a=random(2*PI);
      float v=100;
      dottX[i]=random(width);
      dottY[i]=random(height);
      dottXV[i]=v*sin(a);
      dottYV[i]=v*cos(a);
      dottC[i]=color(random(256),random(256),random(256));
    }
  }

  void drawDott(){
    for (int i=0; i<quantity; i++){
      color c=fade(palette[colr%16],intensity);
      if (colr>15) {fill(c);} else {noFill();}
      stroke(c);
      ellipse(dottX[i],dottY[i],diametr,diametr);
      ellipse(dottX[i]+1,dottY[i],diametr,diametr);
      ellipse(dottX[i],dottY[i]+1,diametr,diametr);
      ellipse(dottX[i]+1,dottY[i]+1,diametr,diametr);

      dottX[i]=dottX[i]+dottXV[i]*(128-speed)/600;
      dottY[i]=dottY[i]+dottYV[i]*(128-speed)/600;
      if (dottX[i]>width+diametr/2) {dottX[i]=dottX[i]-width-diametr/2;}
      if (dottX[i]<-diametr/2) {dottX[i]=dottX[i]+width+diametr/2;}
      if (dottY[i]>height+diametr/2) {dottY[i]=dottY[i]-height-diametr/2;}
      if (dottY[i]<-diametr/2) {dottY[i]=dottY[i]+height+diametr/2;}

    }

  }

  /////////////////////////////////////////////////////////////////////// WALL EFFECT
  void wall(){
    diametr=dmx[dmxAddr+diameterOffs]/10; // thickness of wall
    if (intensity>0) {drawWall();}
  }

  void drawWall(){
    color c=fade(palette[colr%16],intensity);
    fill(c);
    noStroke();
    if ((pan>5)&&(pan<width-5)) {rect(pan-diametr,0,diametr,height);}
    if ((tilt>5)&&(tilt<height-5)) {rect(0,tilt-diametr,width,diametr);}
  }

  /////////////////////////////////////////////////////////////////////// WAWE EFFECT
  float wavePhase;
  void wave(){
    spread=dmx[dmxAddr+spreadOffs]/2;
    diametr=dmx[dmxAddr+diameterOffs]; // thickness of wall
    wavePhase=wavePhase+0.002*(128-dmx[dmxAddr+speedOffs]);

    if (intensity>0) {drawWave();}
  }

  void drawWave(){
    color c=fade(palette[colr%16],intensity);
    noFill();
    stroke(c);
    if ((tilt>5)&&(tilt<height-5)) {
      for (int i=0; i<width; i++){
        float yy=spread*sin(i*2*PI*3/width+wavePhase);
        yy=yy+spread*0.5*sin(i*2*PI*4/width+wavePhase*sin(wavePhase*0.01));
        yy=yy+tilt;
        for (int j=0; j<3; j++) {
          point(i,yy+j);
        }
      }
    }
    if ((pan>5)&&(pan<width-5)) {
      for (int i=0; i<height; i++){
        float xx=spread*sin(i*2*PI*3/height+wavePhase);
        xx=xx+spread*0.5*sin(i*2*PI*4/height+wavePhase*sin(wavePhase*0.01));
        xx=xx+pan;
        for (int j=0; j<3; j++) {
          point(xx+j,i);
        }
      }
    }

  }

  /////////////////////////////////////////////////////////////////////// SOUNDWAVE EFFECT
  void soundWave(){
    spread=dmx[dmxAddr+spreadOffs]*2;
    diametr=dmx[dmxAddr+diameterOffs]; // thickness of wall

    if (intensity>0) {drawSoundWave();}
  }

  void drawSoundWave(){
    color c=fade(palette[colr%16],intensity);
    noFill();
    stroke(c);
    if (colr<16) {// Line wave
      if ((tilt>5)&&(tilt<height-5)) {
        for (int i=0; i<audioInn.bufferSize()-1; i++) {
          line(i, tilt + audioInn.left.get(i)*spread, i, tilt + audioInn.left.get(i+1)*spread);
          line(i, tilt + audioInn.left.get(i)*spread+1, i+1, tilt + audioInn.left.get(i+1)*spread+1);
        }
      }
      if ((pan>5)&&(pan<width-5)) {
        for (int i=0; i<audioInn.bufferSize()-1; i++) {
          line(pan + audioInn.left.get(i)*spread, i, pan + audioInn.left.get(i+1)*spread,i);
          line(pan + audioInn.left.get(i)*spread, i+1, pan + audioInn.left.get(i+1)*spread,i+1);
        }
      } 

    }
    else { // Circle wave
      float  sx=pan;
      float  sy=tilt+(audioInn.left.get(0)*spread+diametr)*cos(0);
      float px=sx;
      float py=sy;
      for (int i=1; i<audioInn.bufferSize()-1; i++) {
        float x=pan+(audioInn.left.get(i)*spread+diametr)*sin(i*2*PI/audioInn.bufferSize());
        float y=tilt+(audioInn.left.get(i)*spread+diametr)*cos(i*2*PI/audioInn.bufferSize());
        line(x,y,px,py);
        line(x+1,y,px+1,py);
        line(x,y+1,px,py+1);
        line(x+1,y+1,px+1,py+1);
        px=x;
        py=y;
      }
      line(sx,sy,px,py);
      line(sx+1,sy,px+1,py);
      line(sx,sy+1,px,py+1);
      line(sx+1,sy+1,px+1,py+1);
    }
  }

   /////////////////////////////////////////////////////////////////////// STROBE EFFECT
  void strobe(){
    spread=dmx[dmxAddr+spreadOffs];  //audio sense. 0=no audio, use speed instead.
    speed=(255-dmx[dmxAddr+speedOffs])/10;
    if (intensity>0) {drawStrobe();}
  }

  int strobeCounter=0;
  boolean strobing=false;
  void drawStrobe(){
    color c=fade(palette[colr%16],intensity);
    if (spread<10) {  // SPEED CONTROLLED STROBE
    if (strobeCounter<speed) {
        strobeCounter++;}
      else {
        strobeCounter=0;
        background(c);
      }
    } else { // SOUND ACTIVATED STROBE
      int peak=0;
      for (int i=0; i<audioInn.bufferSize()-1; i++) {
        int a=int(255*audioInn.right.get(i));
        if (a>peak) {peak=a;}
      }
      if (peak>spread) {
        if (!strobing) {
          background(c);
          strobing=true;
        }
      } else {strobing=false;}

    }
  }

  /////////////////////////////////////////////////////////////////////// GLOBAL SHAPE AFFECTING PAN AND TILT
  float shapePhase=0;
  void shaper(){
    shapePhase=shapePhase+0.002*(128-dmx[dmxAddr+speedOffs]);
    shapeX=int(intensity*2*sin(shapePhase));
    shapeY=int(intensity*2*cos(shapePhase));
  }

  /////////////////////////////////////////////////////////////////////// GOBO IMAGE EFFECT
  void goboImage(){
    if (intensity>0) {drawGoboImage();}
  }

  void drawGoboImage(){
    color c=fade(palette[colr%16],intensity);
    //imageOut(0,pan,tilt);
  }

}

dmx.pde

int identifyDmxStart[] = new int[3];
int maxChannel=32;
int tDmx[]= new int[512];
void receiveDmx(){
  if (connected) {
    for (int i=0; i<512; i++) {
      tDmx[i]=OpenDmx.getValue(i);
    }
    int offset=-1;
    for (int i=0; i<100; i++) {
      if ((tDmx[i]==identifyDmxStart[1])&&(tDmx[i+1]==identifyDmxStart[2])){
        offset=i;
      }
    }
    if (offset>=0) {
      for (int i=0; i<maxChannel; i++) {
        dmx[i]=tDmx[i+offset];
        //print(dmx[i]+"  ");
      }
     // println();
    }
  }
}

void initDmxStart(){
  if (connected) {
    println("Identifying");
    while ((identifyDmxStart[0]!=0)||(identifyDmxStart[1]==0) || (identifyDmxStart[2]==0)) {
      identifyDmxStart[0]=OpenDmx.getValue(0);
      identifyDmxStart[1]=OpenDmx.getValue(1);
       identifyDmxStart[2]=OpenDmx.getValue(2);
    }
    println(identifyDmxStart[1]);
    println("Found!");
  }
}

void loadDmx(){
  String[] lines;
  lines = loadStrings("dmx.txt");
  for (int i=0; i<dmx.length; i++) {
    dmx[i]=int(lines[i]);
  }
}

void saveDmx(){
    String[] lines = new String[dmx.length];
  for (int i = 0; i < dmx.length; i++) {
    lines[i] = str(dmx[i]);
  }
  saveStrings("dmx.txt", lines);
}
Advertisement

5 Responses to Using Video Projectors as Stage Light

    • Hardware issues. The same problems we had with the mini-mixer. The open-DMX usb thing doesn’t handle receiving DMX well. We need the “Pro” model…

  1. Hi,

    Nice job on this!

    This probably is pretty stupid but I can’t seem to get the openDMX libraries in the right place. I’ve put :

    - OpenDmx.jar
    - opendmx.dll
    - FTD2XX.dll

    in : “c:\Users\Image Line\Documents\Processing\libraries”

    but I keep getting : “No library found for com.juanjo.openDmx. As of release 1.0, libraries must be installed in a folder named ‘libraries’ inside the ‘sketchbook’ folder.”

    All input is welcome.

    jmc

    • Ah! I remember we had that problem… The openDmx library is not for processing, but our genius Java guru, Mario Michelli found I way around. You have to make the folder structure in the library (which off-course must be placed in the processing\library folder) the same as the other libraries you have put there.

      On the other hand, the DMX input never worked very well and we ended up using a Behringer BCF2000 MIDI controller to run the show. I if get time I’ll see if I can remove the openDmx references from the code so you can used with Midi or just keyboard.

  2. Some hints on using the patch.

    Using Keyboard:

    s = save current state
    r = load saved state
    R = random. Cool for testing

    Use keys left and right to select “DMX” channel (even if you do not use DMX, we pretend so), and up and down keys to alter the DMX value for selected channel. The output window in the Processing Window will display useful hints as you go.

    12 channels are used for each fixture, so channel 1 to 12 should be parameters like size, position, color, speed etc for the first effect, 13 to 24 for the next and so on.

    Using MIDI:

    The MIDI input uses midi control inputs and translates them to our internal virtual DMX. Midi Contol 0 is translated to DMX channel 1 etc. The Midi value 0-127 is scaled to DMX value 0-255.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s