At this point we've assembled a NeoMatrix and Fadecandy Controller, we tried some examples, and we put some materials in front of the LEDs to see how they look. Now let's try making a Processing sketch from scratch.
In this example, we'll make a simple particle system that creates expanding wavefronts in response to mouse gestures.
Here's how it looks:
In this example, we'll make a simple particle system that creates expanding wavefronts in response to mouse gestures.
Here's how it looks:
(Direct link to the video)
So that's shiny. Let's write some code!
To get the OPC.pde library easily, we'll start by opening the template example included in Fadecandy's processing examples folder. Then we can save that example with a new name. The template starts you out with just enough code to connect to fcserver:
So that's shiny. Let's write some code!
To get the OPC.pde library easily, we'll start by opening the template example included in Fadecandy's processing examples folder. Then we can save that example with a new name. The template starts you out with just enough code to connect to fcserver:
// This is an empty Processing sketch with support for Fadecandy. OPC opc; void setup() { size(600, 300); opc = new OPC(this, "127.0.0.1", 7890); // Set up your LED mapping here } void draw() { background(0); // Draw each frame here }
First, let's set this up to talk to an 8x8 pixel NeoMatrix. Change the setup() to look like this:
void setup() { size(500, 500, P3D); opc = new OPC(this, "127.0.0.1", 7890); opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); }
We made the window square, which is more convenient when we're working with a square NeoMatrix. And while we're at it, we'll select the P3D renderer for Processing. This makes it much faster at drawing images. Since we'll be drawing a large number of particles, this is really important!
Next, we added the ledGrid8x8() command. This is a shortcut for creating 8x8 LED matrices.
You can try running the sketch. At this point, our window should be blank except for the LED matrix:
Next, we added the ledGrid8x8() command. This is a shortcut for creating 8x8 LED matrices.
- 0 is the index number for the first LED in the matrix, since we connected this matrix to output 0 on the Fadecandy board. If we were using output 1, we would start the matrix at LED number 64.
- width/2 and height/2 are the center of the matrix. We place it in the center of the window.
- height/16 is the spacing between LEDs. 16 is double the size of our matrix, so this will make the matrix take up about half the height of the window.
- 0 is the rotation angle. We aren't rotating the matrix away from its default position, so the first LED will be in the top-left corner.
- false here states that the matrix does not zig-zag. Every row of LEDs points the same direction.
You can try running the sketch. At this point, our window should be blank except for the LED matrix:
Now let's add an image. Even though LEDs like this are more about creating interesting light patterns than displaying video, images can be super useful as building-blocks for these light patterns. It may be helpful to think about images as if they're textures or color look-up tables.
In this case, we're creating a particle system that uses many copies of the same image to create a moving field of light. The light will look like expanding wavefronts, and each particle is a ring of light that we can tint however we like.
I made a simple ring texture in Photoshop. You can make your own, or save a copy of mine. If you do, make sure to save the image as a PNG file.
In this case, we're creating a particle system that uses many copies of the same image to create a moving field of light. The light will look like expanding wavefronts, and each particle is a ring of light that we can tint however we like.
I made a simple ring texture in Photoshop. You can make your own, or save a copy of mine. If you do, make sure to save the image as a PNG file.
The texture needs to go in a data folder inside your Processing sketch's folder. Save it as ring.png inside this folder.
Now let's try out this texture! The sketch has a few new parts:
- colorMode() switches to the Hue, Saturation, Brightness color space.
- loadImage() loads ring.png into a PImage variable.
- drawRing() draws our texture with a particular hue, intensity, and location
- Temporary code in draw() puts a single ring at the mouse cursor location
OPC opc; PImage texture; void setup() { size(500, 500, P3D); colorMode(HSB, 100); texture = loadImage("ring.png"); opc = new OPC(this, "127.0.0.1", 7890); opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); } void drawRing(float x, float y, float hue, float intensity, float size) { blendMode(ADD); tint(hue, 50, intensity); image(texture, x - size/2, y - size/2, size, size); } void draw() { background(0); drawRing(mouseX, mouseY, 25, 80, 400); }
You might also notice a little bit of flicker at the edges, where the LEDs that are just barely on rapidly flick between on and off. This is a side-effect of Fadecandy's temporal dithering algorithm. This is how it can simulate brightness levels that are dimmer than the dimmest level the WS2811 controller supports in hardware. The flicker can be distracting when you're looking very closely at the LEDs, but if you're using a diffuser or you're using the LEDs as a light source for your art, this flicker is almost never noticeable.
If you do find the flicker bothersome, you can change the fcserver color management configuration. The documentation describes how you can reconfigure the color correction curves to have a linear section at the dark end, which completely eliminates this flicker at the cost of reduced accuracy in rendering very dark colors.
If you do find the flicker bothersome, you can change the fcserver color management configuration. The documentation describes how you can reconfigure the color correction curves to have a linear section at the dark end, which completely eliminates this flicker at the cost of reduced accuracy in rendering very dark colors.
Now let's make a particle system! I added a Ring class to manage each particle. The individual particles have a lifespan during which they expand and fade. Particles randomly respawn at the mouse cursor location. The mouse cursor location itself is smoothed so that we don't see such discrete bursts of particles every time the mouse moves by a pixel.
I decided to use mouse speed and direction as inputs. Mouse speed controls the intensity that new particles spawn with, and mouse direction controls their hue. As you move your mouse or swipe your touchpad, the speed and angle of your gesture creates a unique pattern of light. The expanding wavefronts from each gesture interact with each other, forming new patterns of light and color.
The Ring class has a respawn() function which computes a new particle's intensity and hue based on the speed and angle of mouse motion. As parameters, it takes the X and Y coordinates for a previous and a new mouse position.
I decided to use mouse speed and direction as inputs. Mouse speed controls the intensity that new particles spawn with, and mouse direction controls their hue. As you move your mouse or swipe your touchpad, the speed and angle of your gesture creates a unique pattern of light. The expanding wavefronts from each gesture interact with each other, forming new patterns of light and color.
The Ring class has a respawn() function which computes a new particle's intensity and hue based on the speed and angle of mouse motion. As parameters, it takes the X and Y coordinates for a previous and a new mouse position.
class Ring { float x, y, size, intensity, hue; void respawn(float x1, float y1, float x2, float y2) { // Start at the newer mouse position x = x2; y = y2; // Intensity is just the distance between mouse points intensity = dist(x1, y1, x2, y2); // Hue is the angle of mouse movement, scaled from -PI..PI to 0..100 hue = map(atan2(y2 - y1, x2 - x1), -PI, PI, 0, 100); // Default size is based on the screen size size = height * 0.1; } void draw() { // Particles fade each frame intensity *= 0.95; // They grow at a rate based on their intensity size += height * intensity * 0.01; // If the particle is still alive, draw it if (intensity >= 1) { blendMode(ADD); tint(hue, 50, intensity); image(texture, x - size/2, y - size/2, size, size); } } };
Now we need to define the rest of the program to drive a system of these Ring particles. Aside from the Ring class, this is what the rest of our program looks like:
OPC opc; PImage texture; Ring rings[]; float smoothX, smoothY; boolean f = false; void setup() { size(500, 500, P3D); colorMode(HSB, 100); texture = loadImage("ring.png"); opc = new OPC(this, "127.0.0.1", 7890); opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); // We can have up to 100 rings. They all start out invisible. rings = new Ring[100]; for (int i = 0; i < rings.length; i++) { rings[i] = new Ring(); } } void draw() { background(0); // Smooth out the mouse location. The smoothX and smoothY variables // move toward the mouse without changing abruptly. float prevX = smoothX; float prevY = smoothY; smoothX += (mouseX - smoothX) * 0.1; smoothY += (mouseY - smoothY) * 0.1; // At every frame, randomly respawn one ring rings[int(random(rings.length))].respawn(prevX, prevY, smoothX, smoothY); // Give each ring a chance to redraw and update for (int i = 0; i < rings.length; i++) { rings[i].draw(); } }
And that's it! You can see the complete source code for this example in GitHub.
Text editor powered by tinymce.