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
Looks brilliant! How did the DMX fail?
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…
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.
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.