├── Fadecandy ├── conf.json └── opc.py ├── README.md └── Touchdesigner_Patches ├── Fadecandy_example.toe └── README.md /Fadecandy/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": [null, 7890], 3 | "verbose": true, 4 | 5 | "color": { 6 | "gamma": 2.5, 7 | "whitepoint": [1.0, 1.0, 1.0] 8 | }, 9 | 10 | "devices": [ 11 | { 12 | "type": "fadecandy", 13 | "map": [ 14 | [ 0, 0, 0, 60 ], 15 | [ 0, 60, 64, 60 ], 16 | [ 0, 120, 128, 60 ], 17 | [ 0, 180, 192, 60 ], 18 | [ 0, 240, 256, 60 ], 19 | [ 0, 300, 320, 60 ], 20 | [ 0, 360, 384, 60 ], 21 | [ 0, 420, 448, 60 ] 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /Fadecandy/opc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Python Client library for Open Pixel Control 4 | http://github.com/zestyping/openpixelcontrol 5 | 6 | Sends pixel values to an Open Pixel Control server to be displayed. 7 | http://openpixelcontrol.org/ 8 | 9 | Recommended use: 10 | 11 | import opc 12 | 13 | # Create a client object 14 | client = opc.Client('localhost:7890') 15 | 16 | # Test if it can connect (optional) 17 | if client.can_connect(): 18 | print 'connected to %s' % ADDRESS 19 | else: 20 | # We could exit here, but instead let's just print a warning 21 | # and then keep trying to send pixels in case the server 22 | # appears later 23 | print 'WARNING: could not connect to %s' % ADDRESS 24 | 25 | # Send pixels forever at 30 frames per second 26 | while True: 27 | my_pixels = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] 28 | if client.put_pixels(my_pixels, channel=0): 29 | print '...' 30 | else: 31 | print 'not connected' 32 | time.sleep(1/30.0) 33 | 34 | """ 35 | 36 | import socket 37 | 38 | class Client(object): 39 | 40 | def __init__(self, server_ip_port, long_connection=True, verbose=False): 41 | """Create an OPC client object which sends pixels to an OPC server. 42 | 43 | server_ip_port should be an ip:port or hostname:port as a single string. 44 | For example: '127.0.0.1:7890' or 'localhost:7890' 45 | 46 | There are two connection modes: 47 | * In long connection mode, we try to maintain a single long-lived 48 | connection to the server. If that connection is lost we will try to 49 | create a new one whenever put_pixels is called. This mode is best 50 | when there's high latency or very high framerates. 51 | * In short connection mode, we open a connection when it's needed and 52 | close it immediately after. This means creating a connection for each 53 | call to put_pixels. Keeping the connection usually closed makes it 54 | possible for others to also connect to the server. 55 | 56 | A connection is not established during __init__. To check if a 57 | connection will succeed, use can_connect(). 58 | 59 | If verbose is True, the client will print debugging info to the console. 60 | 61 | """ 62 | self.verbose = verbose 63 | 64 | self._long_connection = long_connection 65 | 66 | self._ip, self._port = server_ip_port.split(':') 67 | self._port = int(self._port) 68 | 69 | self._socket = None # will be None when we're not connected 70 | 71 | def _debug(self, m): 72 | if self.verbose: 73 | print(' %s' % str(m)) 74 | 75 | def _ensure_connected(self): 76 | """Set up a connection if one doesn't already exist. 77 | 78 | Return True on success or False on failure. 79 | 80 | """ 81 | if self._socket: 82 | self._debug('_ensure_connected: already connected, doing nothing') 83 | return True 84 | 85 | try: 86 | self._debug('_ensure_connected: trying to connect...') 87 | self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 88 | self._socket.connect((self._ip, self._port)) 89 | self._debug('_ensure_connected: ...success') 90 | return True 91 | except socket.error: 92 | self._debug('_ensure_connected: ...failure') 93 | self._socket = None 94 | return False 95 | 96 | def disconnect(self): 97 | """Drop the connection to the server, if there is one.""" 98 | self._debug('disconnecting') 99 | if self._socket: 100 | self._socket.close() 101 | self._socket = None 102 | 103 | def can_connect(self): 104 | """Try to connect to the server. 105 | 106 | Return True on success or False on failure. 107 | 108 | If in long connection mode, this connection will be kept and re-used for 109 | subsequent put_pixels calls. 110 | 111 | """ 112 | success = self._ensure_connected() 113 | if not self._long_connection: 114 | self.disconnect() 115 | return success 116 | 117 | def put_pixels(self, pixels, channel=0): 118 | """Send the list of pixel colors to the OPC server on the given channel. 119 | 120 | channel: Which strand of lights to send the pixel colors to. 121 | Must be an int in the range 0-255 inclusive. 122 | 0 is a special value which means "all channels". 123 | 124 | pixels: A list of 3-tuples representing rgb colors. 125 | Each value in the tuple should be in the range 0-255 inclusive. 126 | For example: [(255, 255, 255), (0, 0, 0), (127, 0, 0)] 127 | Floats will be rounded down to integers. 128 | Values outside the legal range will be clamped. 129 | 130 | Will establish a connection to the server as needed. 131 | 132 | On successful transmission of pixels, return True. 133 | On failure (bad connection), return False. 134 | 135 | The list of pixel colors will be applied to the LED string starting 136 | with the first LED. It's not possible to send a color just to one 137 | LED at a time (unless it's the first one). 138 | 139 | """ 140 | self._debug('put_pixels: connecting') 141 | is_connected = self._ensure_connected() 142 | if not is_connected: 143 | self._debug('put_pixels: not connected. ignoring these pixels.') 144 | return False 145 | 146 | # build OPC message 147 | len_hi_byte = int(len(pixels)*3 / 256) 148 | len_lo_byte = (len(pixels)*3) % 256 149 | header = [channel, 0, len_hi_byte, len_lo_byte] 150 | 151 | pixels = [x for rgb in pixels for x in rgb] 152 | clamp = lambda x: max(min(255, int(x)), 0) 153 | rgbvals = [clamp(x) for x in pixels] 154 | 155 | message = bytes(header + rgbvals) 156 | 157 | #self._debug('put_pixels: sending pixels to server') 158 | try: 159 | self._socket.send(message) 160 | except socket.error: 161 | #self._debug('put_pixels: connection lost. could not send pixels.') 162 | self._socket = None 163 | return False 164 | 165 | if not self._long_connection: 166 | #self._debug('put_pixels: disconnecting') 167 | self.disconnect() 168 | 169 | return True 170 | 171 | 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TouchCandy 2 | ========== 3 | 4 | Sending pixels from TouchDesigner to Fadecandy 5 | 6 | UPDATE: This patch does not work with newer versions of Touchdesigner. 7 | To remedy this, you have to convert the table to output values between 1-255 instead of 0-255. Will update the patch asap 8 | 9 | 10 | --- 11 | 12 | 13 | Based on Micah Scott's Fadecandy project: https://github.com/scanlime/fadecandy 14 | 15 | Fadecandy forum: https://groups.google.com/forum/#!forum/fadecandy 16 | 17 | You can download Touch from: https://www.derivative.ca/088/Downloads/ 18 | 19 | 20 | The modified opc.py included in this repo, goes to: \Derivative\TouchDesigner088\bin\Lib 21 | 22 | The included conf.json is modified for 60 pixel strips. 23 | 24 | The TouchDesigner patch is a simple network, converting video TOP to text based DAT. 25 | The 'executeDAT' in Touch, sends the pixels to the server, which you need to have in the receiving end. 26 | 27 | ** 28 | -------------------------------------------------------------------------------- /Touchdesigner_Patches/Fadecandy_example.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Swaeg/TouchCandy/603759eb1fe72aac771f41c36fb28effc9cbe0e5/Touchdesigner_Patches/Fadecandy_example.toe -------------------------------------------------------------------------------- /Touchdesigner_Patches/README.md: -------------------------------------------------------------------------------- 1 | This example patch works with the modified opc.py & conf.json, included in this repo. 2 | It has a simple TOP -> DAT network to enable running video into the LED's. 3 | --------------------------------------------------------------------------------