WS2811 Pixel Control from Syphon Video via Fadecandy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"listen": ["127.0.0.1", 7890], | |
"verbose": true, | |
"color": { | |
"gamma": 2.5, | |
"whitepoint": [1.0, 1.0, 1.0], | |
"linearSlope": 1.0, | |
"linearCutoff": 0.0 | |
}, | |
"devices": [ | |
{ | |
"type": "fadecandy", | |
"serial": "GIVHQUDXLZYTODOF", | |
"led": null, | |
"dither": true, | |
"interpolate": true, | |
"map": [ | |
[0, 0, 0, 50], | |
[0, 50, 64, 50], | |
[0, 100, 128, 50], | |
[0, 150, 192, 50], | |
[0, 200, 256, 50], | |
[0, 250, 320, 50], | |
[0, 300, 384, 50], | |
[0, 350, 448, 50] | |
] | |
}, | |
{ | |
"type": "fadecandy", | |
"serial": "IKYISOAUTVYZYHAD", | |
"led": null, | |
"dither": true, | |
"interpolate": true, | |
"map": [ | |
[0, 400, 0, 50], | |
[0, 450, 64, 50], | |
[0, 500, 128, 50], | |
[0, 550, 192, 50], | |
[0, 600, 256, 50], | |
[0, 650, 320, 50], | |
[0, 700, 384, 50], | |
[0, 750, 448, 50] | |
] | |
}, | |
{ | |
"type": "fadecandy", | |
"serial": "UGQKPJMAESHZDLEV", | |
"led": null, | |
"dither": true, | |
"interpolate": true, | |
"map": [ | |
[0, 800, 0, 50], | |
[0, 850, 64, 50], | |
[0, 900, 128, 50], | |
[0, 950, 192, 50], | |
[0, 1000, 256, 50], | |
[0, 1050, 320, 50], | |
[0, 1100, 384, 50], | |
[0, 1150, 448, 50] | |
] | |
}, | |
{ | |
"type": "fadecandy", | |
"serial": "VNVFSAAGUKFJJRWX", | |
"led": null, | |
"dither": true, | |
"interpolate": true, | |
"map": [ | |
[0, 1200, 0, 50], | |
[0, 1250, 64, 50], | |
[0, 1300, 128, 50], | |
[0, 1350, 192, 50], | |
[0, 1400, 256, 50], | |
[0, 1450, 320, 50], | |
[0, 1500, 384, 50], | |
[0, 1550, 448, 50] | |
] | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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. | |
* | |
* abozzay, Nov 2017 | |
* Updated for Processing 3 | |
*/ | |
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; | |
registerMethod("draw", 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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import codeanticode.syphon.*; | |
OPC opc; | |
PImage img; | |
SyphonClient client; | |
void setup() | |
{ | |
size(1344, 384, P2D); | |
surface.setAlwaysOnTop(true); | |
frameRate(30); | |
println("Press ESC to exit, d to list the connected server.\n"); | |
// List Syphon Clients | |
println("Available Syphon servers:"); | |
println(SyphonClient.listServers()); | |
// Create syhpon client to receive frames | |
// from the first available running server: | |
client = new SyphonClient(this); | |
// Connect to the local instance of fcserver | |
opc = new OPC(this, "127.0.0.1", 7890); | |
// Draw the pixels on the screen | |
opc.showLocations(true); | |
/* ledGrid(index, stripLength, numStrips, x, y, ledSpacing, stripSpacing, angle, zigzag) | |
- Place a rigid grid of LEDs on the screen | |
- index: Number for the first LED in the grid, starting with zero | |
- stripLength: How long is each strip in the grid? | |
- numStrips: How many strips of LEDs make up the grid? | |
- x, y: Center location, in pixels | |
- ledSpacing: Spacing between LEDs, in pixels | |
- stripSpacing: Spacing between strips, in pixels | |
- angle: Angle, in radians. Positive is clockwise. 0 has pixels in a strip going left-to-right and strips going top-to-bottom. | |
- zigzag: true = Every other strip is reversed, false = All strips are non-reversed */ | |
// 64 strips going right to left, each strip 50, 25 top to bottom 25 to top | |
opc.ledGrid(0, 25, 64, width/2, height/2, height/25, width/64, radians(90), true); | |
} | |
void draw() | |
{ | |
background(0); | |
if (client.newFrame()) { | |
img = client.getImage(img); | |
if (img != null) { | |
image(img, 0, 0, width, height); | |
} | |
} | |
} | |
void keyPressed() { | |
if (key == ESC) { | |
client.stop(); | |
} else if (key == 'd') { | |
println("Connected to:"); | |
println(client.getServerName()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment