Skip to content

Instantly share code, notes, and snippets.

@eightlines
Last active February 28, 2019 15:20
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eightlines/f3000fa31ce3da9de932 to your computer and use it in GitHub Desktop.
Save eightlines/f3000fa31ce3da9de932 to your computer and use it in GitHub Desktop.
FadeCandy Syphon Display

I wanted to see how some GLES code looked on an 8x8 grid driven by the FadeCandy. This script uses Syphon to mirror the on screen display.

Requirements (Mac only):

Running:

  • Start the FadeCandy OPC Server
  • Enable the Syphon Virtual Screen
  • Drag window into the Virtual Screen to mirror on the FadeCandy display (This script uses the 8x8 Grid)
  • Example: http://glsl.heroku.com/e#8289.3 (Large slow moving blobs look the best on the display)
import codeanticode.syphon.*;
OPC opc;
SyphonClient client;
PImage dot;
PImage img;
void setup() {
size(640, 360, P3D);
client = new SyphonClient(this);
opc = new OPC(this, "127.0.0.1", 7890);
opc.ledGrid8x8(0, width / 2, height / 2, height / 12.0, 0, false);
}
void draw() {
background(0);
if (client.available()) {
img = client.getImage(img);
image(img, 0, 0, width, height);
}
}
/*
* Simple Open Pixel Control client for Processing,
* designed to sample each LED's color from some point on the canvas.
*
* Micah Elizabeth Scott, 2013
* This file is released into the public domain.
*/
import java.net.*;
import java.util.Arrays;
public class OPC
{
Socket socket;
OutputStream output;
String host;
int port;
int[] pixelLocations;
byte[] packetData;
byte firmwareConfig;
String colorCorrection;
boolean enableShowLocations;
OPC(PApplet parent, String host, int port)
{
this.host = host;
this.port = port;
this.enableShowLocations = true;
parent.registerDraw(this);
}
// Set the location of a single LED
void led(int index, int x, int y)
{
// For convenience, automatically grow the pixelLocations array. We do want this to be an array,
// instead of a HashMap, to keep draw() as fast as it can be.
if (pixelLocations == null) {
pixelLocations = new int[index + 1];
} else if (index >= pixelLocations.length) {
pixelLocations = Arrays.copyOf(pixelLocations, index + 1);
}
pixelLocations[index] = x + width * y;
}
// Set the location of several LEDs arranged in a strip.
// Angle is in radians, measured clockwise from +X.
// (x,y) is the center of the strip.
void ledStrip(int index, int count, float x, float y, float spacing, float angle, boolean reversed)
{
float s = sin(angle);
float c = cos(angle);
for (int i = 0; i < count; i++) {
led(reversed ? (index + count - 1 - i) : (index + i),
(int)(x + (i - (count-1)/2.0) * spacing * c + 0.5),
(int)(y + (i - (count-1)/2.0) * spacing * s + 0.5));
}
}
// Set the location of several LEDs arranged in a grid. The first strip is
// at 'angle', measured in radians clockwise from +X.
// (x,y) is the center of the grid.
void ledGrid(int index, int stripLength, int numStrips, float x, float y,
float ledSpacing, float stripSpacing, float angle, boolean zigzag)
{
float s = sin(angle + HALF_PI);
float c = cos(angle + HALF_PI);
for (int i = 0; i < numStrips; i++) {
ledStrip(index + stripLength * i, stripLength,
x + (i - (numStrips-1)/2.0) * stripSpacing * c,
y + (i - (numStrips-1)/2.0) * stripSpacing * s, ledSpacing,
angle, zigzag && (i % 2) == 1);
}
}
// Set the location of 64 LEDs arranged in a uniform 8x8 grid.
// (x,y) is the center of the grid.
void ledGrid8x8(int index, float x, float y, float spacing, float angle, boolean zigzag)
{
ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag);
}
// Should the pixel sampling locations be visible? This helps with debugging.
// Showing locations is enabled by default. You might need to disable it if our drawing
// is interfering with your processing sketch, or if you'd simply like the screen to be
// less cluttered.
void showLocations(boolean enabled)
{
enableShowLocations = enabled;
}
// Enable or disable dithering. Dithering avoids the "stair-stepping" artifact and increases color
// resolution by quickly jittering between adjacent 8-bit brightness levels about 400 times a second.
// Dithering is on by default.
void setDithering(boolean enabled)
{
if (enabled)
firmwareConfig &= ~0x01;
else
firmwareConfig |= 0x01;
sendFirmwareConfigPacket();
}
// Enable or disable frame interpolation. Interpolation automatically blends between consecutive frames
// in hardware, and it does so with 16-bit per channel resolution. Combined with dithering, this helps make
// fades very smooth. Interpolation is on by default.
void setInterpolation(boolean enabled)
{
if (enabled)
firmwareConfig &= ~0x02;
else
firmwareConfig |= 0x02;
sendFirmwareConfigPacket();
}
// Put the Fadecandy onboard LED under automatic control. It blinks any time the firmware processes a packet.
// This is the default configuration for the LED.
void statusLedAuto()
{
firmwareConfig &= 0x0C;
sendFirmwareConfigPacket();
}
// Manually turn the Fadecandy onboard LED on or off. This disables automatic LED control.
void setStatusLed(boolean on)
{
firmwareConfig |= 0x04; // Manual LED control
if (on)
firmwareConfig |= 0x08;
else
firmwareConfig &= ~0x08;
sendFirmwareConfigPacket();
}
// Set the color correction parameters
void setColorCorrection(float gamma, float red, float green, float blue)
{
colorCorrection = "{ \"gamma\": " + gamma + ", \"whitepoint\": [" + red + "," + green + "," + blue + "]}";
sendColorCorrectionPacket();
}
// Set custom color correction parameters from a string
void setColorCorrection(String s)
{
colorCorrection = s;
sendColorCorrectionPacket();
}
// Send a packet with the current firmware configuration settings
void sendFirmwareConfigPacket()
{
if (output == null) {
// We'll do this when we reconnect
return;
}
byte[] packet = new byte[9];
packet[0] = 0; // Channel (reserved)
packet[1] = (byte)0xFF; // Command (System Exclusive)
packet[2] = 0; // Length high byte
packet[3] = 5; // Length low byte
packet[4] = 0x00; // System ID high byte
packet[5] = 0x01; // System ID low byte
packet[6] = 0x00; // Command ID high byte
packet[7] = 0x02; // Command ID low byte
packet[8] = firmwareConfig;
try {
output.write(packet);
} catch (Exception e) {
dispose();
}
}
// Send a packet with the current color correction settings
void sendColorCorrectionPacket()
{
if (colorCorrection == null) {
// No color correction defined
return;
}
if (output == null) {
// We'll do this when we reconnect
return;
}
byte[] content = colorCorrection.getBytes();
int packetLen = content.length + 4;
byte[] header = new byte[8];
header[0] = 0; // Channel (reserved)
header[1] = (byte)0xFF; // Command (System Exclusive)
header[2] = (byte)(packetLen >> 8);
header[3] = (byte)(packetLen & 0xFF);
header[4] = 0x00; // System ID high byte
header[5] = 0x01; // System ID low byte
header[6] = 0x00; // Command ID high byte
header[7] = 0x01; // Command ID low byte
try {
output.write(header);
output.write(content);
} catch (Exception e) {
dispose();
}
}
// Automatically called at the end of each draw().
// This handles the automatic Pixel to LED mapping.
// If you aren't using that mapping, this function has no effect.
// In that case, you can call setPixelCount(), setPixel(), and writePixels()
// separately.
void draw()
{
if (pixelLocations == null) {
// No pixels defined yet
return;
}
if (output == null) {
// Try to (re)connect
connect();
}
if (output == null) {
return;
}
int numPixels = pixelLocations.length;
int ledAddress = 4;
setPixelCount(numPixels);
loadPixels();
for (int i = 0; i < numPixels; i++) {
int pixelLocation = pixelLocations[i];
int pixel = pixels[pixelLocation];
packetData[ledAddress] = (byte)(pixel >> 16);
packetData[ledAddress + 1] = (byte)(pixel >> 8);
packetData[ledAddress + 2] = (byte)pixel;
ledAddress += 3;
if (enableShowLocations) {
pixels[pixelLocation] = 0xFFFFFF ^ pixel;
}
}
writePixels();
if (enableShowLocations) {
updatePixels();
}
}
// Change the number of pixels in our output packet.
// This is normally not needed; the output packet is automatically sized
// by draw() and by setPixel().
void setPixelCount(int numPixels)
{
int numBytes = 3 * numPixels;
int packetLen = 4 + numBytes;
if (packetData == null || packetData.length != packetLen) {
// Set up our packet buffer
packetData = new byte[packetLen];
packetData[0] = 0; // Channel
packetData[1] = 0; // Command (Set pixel colors)
packetData[2] = (byte)(numBytes >> 8);
packetData[3] = (byte)(numBytes & 0xFF);
}
}
// Directly manipulate a pixel in the output buffer. This isn't needed
// for pixels that are mapped to the screen.
void setPixel(int number, color c)
{
int offset = 4 + number * 3;
if (packetData == null || packetData.length < offset + 3) {
setPixelCount(number + 1);
}
packetData[offset] = (byte) (c >> 16);
packetData[offset + 1] = (byte) (c >> 8);
packetData[offset + 2] = (byte) c;
}
// Read a pixel from the output buffer. If the pixel was mapped to the display,
// this returns the value we captured on the previous frame.
color getPixel(int number)
{
int offset = 4 + number * 3;
if (packetData == null || packetData.length < offset + 3) {
return 0;
}
return (packetData[offset] << 16) | (packetData[offset + 1] << 8) | packetData[offset + 2];
}
// Transmit our current buffer of pixel values to the OPC server. This is handled
// automatically in draw() if any pixels are mapped to the screen, but if you haven't
// mapped any pixels to the screen you'll want to call this directly.
void writePixels()
{
if (packetData == null || packetData.length == 0) {
// No pixel buffer
return;
}
if (output == null) {
// Try to (re)connect
connect();
}
if (output == null) {
return;
}
try {
output.write(packetData);
} catch (Exception e) {
dispose();
}
}
void dispose()
{
// Destroy the socket. Called internally when we've disconnected.
if (output != null) {
println("Disconnected from OPC server");
}
socket = null;
output = null;
}
void connect()
{
// Try to connect to the OPC server. This normally happens automatically in draw()
try {
socket = new Socket(host, port);
socket.setTcpNoDelay(true);
output = socket.getOutputStream();
println("Connected to OPC server");
} catch (ConnectException e) {
dispose();
} catch (IOException e) {
dispose();
}
sendColorCorrectionPacket();
sendFirmwareConfigPacket();
}
}
@dpentecost
Copy link

Hey Brent - Thanks for posting this. I had struggled a bit and almost got it before I found this page, which confirmed my code was right. But I was trying VDMX as a source and I got flashing on my 8x8 until I took out the if (client.available()) test. Leaving this here for anyone else going the same route. Happy New Year!

Dave

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment