Commit | Line | Data |
---|---|---|
e73ef85d MS |
1 | import netP5.*; |
2 | import oscP5.*; | |
3 | ||
19ea62fd | 4 | |
e73ef85d MS |
5 | /** |
6 | * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND | |
7 | * | |
8 | * //\\ //\\ //\\ //\\ | |
9 | * ///\\\ ///\\\ ///\\\ ///\\\ | |
10 | * \\\/// \\\/// \\\/// \\\/// | |
11 | * \\// \\// \\// \\// | |
12 | * | |
13 | * EXPERTS ONLY!! EXPERTS ONLY!! | |
14 | * | |
15 | * This class implements the output function to the Panda Boards. It | |
16 | * will be moved into GLucose once stabilized. | |
17 | */ | |
b58e5a1d | 18 | public static class PandaDriver { |
d626bc9b MS |
19 | |
20 | interface Listener { | |
21 | public void onToggle(boolean enabled); | |
22 | } | |
23 | ||
24 | private Listener listener = null; | |
25 | ||
79ae8245 MS |
26 | // IP address |
27 | public final String ip; | |
19ea62fd AK |
28 | |
29 | public PandaMapping pm; | |
79ae8245 | 30 | |
ec017591 MS |
31 | private final static int PORT = 779; |
32 | ||
33 | private final DatagramSocket socket; | |
34 | ||
e73ef85d MS |
35 | // Address to send to |
36 | private final NetAddress address; | |
37 | ||
79ae8245 MS |
38 | // Whether board output is enabled |
39 | private boolean enabled = false; | |
19ea62fd AK |
40 | |
41 | // Frame count for Grizzlies | |
42 | private int frameNum = 1; | |
79ae8245 | 43 | |
e73ef85d MS |
44 | // OSC message |
45 | private final OscMessage message; | |
46 | ||
a922e963 | 47 | // List of point indices that get sent to this board |
e4d0d812 | 48 | private final int[] points; |
44b8de9c | 49 | |
e73ef85d | 50 | // Packet data |
19ea62fd | 51 | private final byte[] packet = new byte[4*280]; // magic number, our UDP packet size |
e73ef85d | 52 | |
84086fa3 MS |
53 | private static final int NO_POINT = -1; |
54 | ||
19ea62fd AK |
55 | private Model _model; |
56 | ||
a3ccf23a MS |
57 | //////////////////////////////////////////////////////////////// |
58 | // | |
59 | // READ THIS RIGHT NOW BEFORE YOU MODIFY THE BELOW!!!!!!!!!!!!! | |
60 | // READ THIS RIGHT NOW BEFORE YOU MODIFY THE BELOW!!!!!!!!!!!!! | |
61 | // READ THIS RIGHT NOW BEFORE YOU MODIFY THE BELOW!!!!!!!!!!!!! | |
62 | // | |
63 | // The mappings below indicate the physical order of strips | |
64 | // connected to a pandaboard channel. The strip numbers are a | |
65 | // reflection of how the model is built. | |
66 | // | |
67 | // For ANYTHING in the model which is a rectangular prism, | |
68 | // which means Cubes, the BassBox, and each Speaker, the | |
69 | // strips are numbered incrementally by face. The first | |
70 | // face is always the FRONT, which you are looking at. | |
71 | // The next face is the RIGHT, then the BACK, then the LEFT. | |
72 | // | |
73 | // For every face, the strips are ordered numerically moving | |
74 | // clockwise from the the TOP LEFT. | |
75 | // | |
76 | // So, for a cube: | |
77 | // | |
78 | // Strip 0: front face, top strip, left to right | |
79 | // Strip 1: front face, right strip, top to bottom | |
80 | // Strip 2: front face, bottom strip, right to left | |
81 | // Strip 3: front face, left strip, bottom to top | |
82 | // | |
83 | // Strip 4: right face, top strip, left to right | |
84 | // ... and so on | |
85 | // Strip 14: left face, bottom strip, right to left | |
86 | // Strip 15: left face, left strip, bottom to top | |
87 | // | |
88 | //////////////////////////////////////////////////////////////// | |
89 | ||
d626bc9b MS |
90 | private final static int FORWARD = -1; |
91 | private final static int BACKWARD = -2; | |
a3ccf23a | 92 | |
a922e963 MS |
93 | /** |
94 | * These constant arrays indicate the order in which the strips of a cube | |
95 | * are wired. There are four different options, depending on which bottom | |
96 | * corner of the cube the data wire comes in. | |
97 | */ | |
29674806 | 98 | private final static int[][] CUBE_STRIP_ORDERINGS = new int[][] { |
270a8b44 MS |
99 | // { 2, 1, 0, 3, 13, 12, 15, 14, 4, 7, 6, 5, 11, 10, 9, 8 }, // FRONT_LEFT |
100 | // { 6, 5, 4, 7, 1, 0, 3, 2, 8, 11, 10, 9, 15, 14, 13, 12 }, // FRONT_RIGHT | |
101 | // { 14, 13, 12, 15, 9, 8, 11, 10, 0, 3, 2, 1, 7, 6, 5, 4 }, // REAR_LEFT | |
102 | // { 10, 9, 8, 11, 5, 4, 7, 6, 12, 15, 14, 13, 3, 2, 1, 0 }, // REAR_RIGHT | |
103 | ||
104 | ||
a922e963 MS |
105 | { 2, 1, 0, 3, 13, 12, 15, 14, 4, 7, 6, 5, 11, 10, 9, 8 }, // FRONT_LEFT |
106 | { 6, 5, 4, 7, 1, 0, 3, 2, 8, 11, 10, 9, 15, 14, 13, 12 }, // FRONT_RIGHT | |
107 | { 14, 13, 12, 15, 9, 8, 11, 10, 0, 3, 2, 1, 7, 6, 5, 4 }, // REAR_LEFT | |
270a8b44 MS |
108 | { 9, 8, 11, 5, 4, 7, 6, 10, 14, 2, 1, 0, 3, 13, 12, 15 }, // REAR_RIGHT |
109 | ||
a922e963 | 110 | }; |
29674806 MS |
111 | |
112 | private final static int[][] BASS_STRIP_ORDERING = { | |
1d75c8a9 | 113 | // front face, counterclockwise from bottom front left |
a3ccf23a MS |
114 | {2, BACKWARD /* if this strip has extra pixels, you can add them here */ /*, 4 */ }, |
115 | {1, BACKWARD /* if this strip is short some pixels, substract them here */ /*, -3 */ }, | |
1d75c8a9 MS |
116 | {0, BACKWARD }, |
117 | {3, BACKWARD }, | |
118 | ||
119 | // left face, counterclockwise from bottom front left | |
120 | {13, BACKWARD }, | |
121 | {12, BACKWARD }, | |
122 | {15, BACKWARD }, | |
123 | {14, BACKWARD }, | |
124 | ||
125 | // back face, counterclockwise from bottom rear left | |
126 | {9, BACKWARD }, | |
127 | {8, BACKWARD }, | |
128 | {11, BACKWARD }, | |
129 | {10, BACKWARD }, | |
130 | ||
131 | // right face, counterclockwise from bottom rear right | |
132 | {5, BACKWARD }, | |
133 | {4, BACKWARD }, | |
134 | {7, BACKWARD }, | |
135 | {6, BACKWARD }, | |
136 | }; | |
137 | ||
138 | private final static int[][] STRUT_STRIP_ORDERING = { | |
139 | {6, BACKWARD}, | |
140 | {5, FORWARD}, | |
141 | {4, BACKWARD}, | |
142 | {3, FORWARD}, | |
143 | {2, BACKWARD}, | |
144 | {1, FORWARD}, | |
a3ccf23a | 145 | {0, BACKWARD}, |
1d75c8a9 | 146 | {7, FORWARD}, |
29674806 MS |
147 | }; |
148 | ||
149 | private final static int[][] FLOOR_STRIP_ORDERING = { | |
150 | {0, FORWARD}, | |
151 | {1, FORWARD}, | |
152 | {2, FORWARD}, | |
1d75c8a9 | 153 | {3, BACKWARD}, |
29674806 MS |
154 | }; |
155 | ||
1d75c8a9 MS |
156 | // The speakers are currently configured to be wired the same |
157 | // as cubes with Wiring.FRONT_LEFT. If this needs to be changed, | |
158 | // remove this null assignment and change the below to have mappings | |
159 | // for the LEFT and RIGHT speaker | |
b58e5a1d | 160 | private final static int[][][] SPEAKER_STRIP_ORDERING = { |
1d75c8a9 MS |
161 | // Left speaker |
162 | { | |
163 | // Front face, counter-clockwise from bottom left | |
164 | {2, BACKWARD }, | |
165 | {1, BACKWARD }, | |
166 | {0, BACKWARD }, | |
167 | {3, BACKWARD }, | |
168 | }, | |
169 | // Right speaker | |
170 | { | |
171 | // Front face, counter-clockwise from bottom left | |
172 | {2, BACKWARD }, | |
173 | {1, BACKWARD }, | |
174 | {0, BACKWARD }, | |
175 | {3, BACKWARD }, | |
176 | } | |
29674806 | 177 | }; |
d626bc9b MS |
178 | |
179 | public PandaDriver(String ip) { | |
180 | this.ip = ip; | |
181 | ||
182 | // Initialize our OSC output stuff | |
19ea62fd | 183 | address = new NetAddress(ip, 779); |
d626bc9b MS |
184 | message = new OscMessage("/shady/pointbuffer"); |
185 | ||
ec017591 MS |
186 | try { |
187 | socket = new DatagramSocket(); | |
188 | } catch (Exception x) { | |
189 | throw new RuntimeException(x); | |
190 | } | |
191 | ||
d626bc9b MS |
192 | // Build the array of points, initialize all to nothing |
193 | points = new int[PandaMapping.PIXELS_PER_BOARD]; | |
194 | for (int i = 0; i < points.length; ++i) { | |
195 | points[i] = NO_POINT; | |
196 | } | |
197 | } | |
198 | ||
19ea62fd | 199 | public PandaDriver(String ip, Model model, PandaMapping _pm) { |
44b8de9c | 200 | this(ip); |
19ea62fd AK |
201 | pm = _pm; |
202 | _model = model; | |
a922e963 MS |
203 | // Ok, we are initialized, time to build the array if points in order to |
204 | // send out. We start at the head of our point buffer, and work our way | |
205 | // down. This is the order in which points will be sent down the wire. | |
206 | int ci = -1; | |
207 | ||
19ea62fd | 208 | // Iterate through all our channelq s |
84086fa3 | 209 | for (ChannelMapping channel : pm.channelList) { |
a922e963 MS |
210 | ++ci; |
211 | int pi = ci * ChannelMapping.PIXELS_PER_CHANNEL; | |
212 | ||
84086fa3 | 213 | switch (channel.mode) { |
a922e963 | 214 | |
84086fa3 | 215 | case ChannelMapping.MODE_CUBES: |
a922e963 | 216 | // We have a list of cubes per channel |
84086fa3 MS |
217 | for (int rawCubeIndex : channel.objectIndices) { |
218 | if (rawCubeIndex < 0) { | |
a922e963 MS |
219 | // No cube here, skip ahead in the buffer |
220 | pi += Cube.POINTS_PER_CUBE; | |
84086fa3 | 221 | } else { |
a922e963 MS |
222 | // The cube exists, check which way it is wired to |
223 | // figure out the order of strips. | |
84086fa3 MS |
224 | Cube cube = model.getCubeByRawIndex(rawCubeIndex); |
225 | int stripOrderIndex = 0; | |
226 | switch (cube.wiring) { | |
227 | case FRONT_LEFT: stripOrderIndex = 0; break; | |
228 | case FRONT_RIGHT: stripOrderIndex = 1; break; | |
229 | case REAR_LEFT: stripOrderIndex = 2; break; | |
230 | case REAR_RIGHT: stripOrderIndex = 3; break; | |
231 | } | |
a922e963 | 232 | |
ec017591 MS |
233 | // TODO(mcslee): clean up, ordering always consistent now |
234 | stripOrderIndex = 2; | |
235 | ||
a922e963 MS |
236 | // Iterate through all the strips on the cube and add the points |
237 | for (int stripIndex : CUBE_STRIP_ORDERINGS[stripOrderIndex]) { | |
238 | // We go backwards here... in the model strips go clockwise, but | |
239 | // the physical wires are run counter-clockwise | |
29674806 | 240 | pi = mapStrip(cube.strips.get(stripIndex), BACKWARD, points, pi); |
84086fa3 MS |
241 | } |
242 | } | |
e73ef85d | 243 | } |
84086fa3 MS |
244 | break; |
245 | ||
246 | case ChannelMapping.MODE_BASS: | |
29674806 MS |
247 | for (int[] config : BASS_STRIP_ORDERING) { |
248 | pi = mapStrip(model.bassBox.strips.get(config[0]), config[1], points, pi); | |
1d75c8a9 | 249 | if (config.length >= 3) pi += config[2]; |
29674806 | 250 | } |
84086fa3 MS |
251 | break; |
252 | ||
1d75c8a9 MS |
253 | case ChannelMapping.MODE_STRUTS_AND_FLOOR: |
254 | for (int[] config : STRUT_STRIP_ORDERING) { | |
255 | pi = mapStrip(model.bassBox.struts.get(config[0]), config[1], points, pi); | |
256 | if (config.length >= 3) pi += config[2]; | |
257 | } | |
29674806 MS |
258 | for (int[] config : FLOOR_STRIP_ORDERING) { |
259 | pi = mapStrip(model.boothFloor.strips.get(config[0]), config[1], points, pi); | |
1d75c8a9 | 260 | if (config.length >= 3) pi += config[2]; |
29674806 | 261 | } |
84086fa3 MS |
262 | break; |
263 | ||
264 | case ChannelMapping.MODE_SPEAKER: | |
1d75c8a9 MS |
265 | int [][] speakerStripOrdering; |
266 | if (SPEAKER_STRIP_ORDERING == null) { | |
267 | // Copy the cube strip ordering | |
268 | int[] frontLeftCubeWiring = CUBE_STRIP_ORDERINGS[0]; | |
269 | speakerStripOrdering = new int[frontLeftCubeWiring.length][]; | |
270 | for (int i = 0; i < frontLeftCubeWiring.length; ++i) { | |
271 | speakerStripOrdering[i] = new int[] { frontLeftCubeWiring[0], BACKWARD }; | |
272 | } | |
273 | } else { | |
274 | speakerStripOrdering = SPEAKER_STRIP_ORDERING[channel.objectIndices[0]]; | |
275 | } | |
276 | for (int[] config : speakerStripOrdering) { | |
29674806 MS |
277 | Speaker speaker = model.speakers.get(channel.objectIndices[0]); |
278 | pi = mapStrip(speaker.strips.get(config[0]), config[1], points, pi); | |
1d75c8a9 | 279 | if (config.length >= 3) pi += config[2]; |
29674806 | 280 | } |
84086fa3 MS |
281 | break; |
282 | ||
283 | case ChannelMapping.MODE_NULL: | |
a922e963 | 284 | // No problem, nothing on this channel! |
84086fa3 MS |
285 | break; |
286 | ||
287 | default: | |
288 | throw new RuntimeException("Invalid/unhandled channel mapping mode: " + channel.mode); | |
e73ef85d | 289 | } |
a922e963 | 290 | |
e73ef85d | 291 | } |
e73ef85d | 292 | } |
29674806 MS |
293 | |
294 | private int mapStrip(Strip s, int direction, int[] points, int pi) { | |
ec017591 MS |
295 | return mapStrip(s, direction, points, pi, s.points.size()); |
296 | } | |
297 | ||
298 | private int mapStrip(Strip s, int direction, int[] points, int pi, int len) { | |
29674806 | 299 | if (direction == FORWARD) { |
ec017591 | 300 | int i = 0; |
2bb56822 | 301 | for (LXPoint p : s.points) { |
29674806 | 302 | points[pi++] = p.index; |
ec017591 MS |
303 | if (++i >= len) { |
304 | break; | |
305 | } | |
29674806 MS |
306 | } |
307 | } else if (direction == BACKWARD) { | |
ec017591 | 308 | for (int i = len-1; i >= 0; --i) { |
29674806 MS |
309 | points[pi++] = s.points.get(i).index; |
310 | } | |
311 | } else { | |
312 | throw new RuntimeException("Unidentified strip mapping direction: " + direction); | |
313 | } | |
314 | return pi; | |
315 | } | |
e73ef85d | 316 | |
d626bc9b MS |
317 | public PandaDriver setListener(Listener listener) { |
318 | this.listener = listener; | |
319 | return this; | |
320 | } | |
321 | ||
322 | public void setEnabled(boolean enabled) { | |
323 | if (this.enabled != enabled) { | |
324 | this.enabled = enabled; | |
325 | println("PandaBoard/" + ip + ": " + (enabled ? "ON" : "OFF")); | |
326 | if (listener != null) { | |
327 | listener.onToggle(enabled); | |
328 | } | |
1d75c8a9 MS |
329 | } |
330 | } | |
1f42cce7 MS |
331 | |
332 | public boolean isEnabled() { | |
333 | return this.enabled; | |
334 | } | |
d626bc9b MS |
335 | |
336 | public void disable() { | |
337 | setEnabled(false); | |
338 | } | |
1d75c8a9 MS |
339 | |
340 | public void enable() { | |
d626bc9b | 341 | setEnabled(true); |
1d75c8a9 MS |
342 | } |
343 | ||
a922e963 | 344 | public void toggle() { |
d626bc9b | 345 | setEnabled(!enabled); |
a922e963 MS |
346 | } |
347 | ||
ec017591 MS |
348 | private final int[] GRIZZLY_STRIP_ORDERING = new int[] { 9, 8, 11, 5, 4, 7, 6, 10, 14, 2, 1, 0, 3, 13, 12, 15 }; |
349 | ||
e73ef85d | 350 | public final void send(int[] colors) { |
b58e5a1d | 351 | if (!enabled) { |
79ae8245 MS |
352 | return; |
353 | } | |
19ea62fd | 354 | frameNum++; |
e73ef85d MS |
355 | int len = 0; |
356 | int packetNum = 0; | |
19ea62fd | 357 | for (ChannelMapping channel : pm.channelList) { |
ec017591 MS |
358 | for (int rawCubeIndex : channel.objectIndices) { |
359 | if (rawCubeIndex > 0) { | |
360 | Cube cube = _model.getCubeByRawIndex(rawCubeIndex); | |
361 | ||
362 | // TODO(mcslee): clean this up, precompute paths | |
363 | for (int stripIndex : GRIZZLY_STRIP_ORDERING) { | |
364 | Strip strip = cube.strips.get(stripIndex); | |
365 | int stripLen = ((stripIndex == 9) || (stripIndex == 16)) ? 15 : 16; | |
366 | for (int i = stripLen-1; i >= 0; --i) { | |
367 | int c = colors[strip.points.get(i).index]; | |
19ea62fd AK |
368 | byte r = (byte) ((c >> 16) & 0xFF); |
369 | byte g = (byte) ((c >> 8) & 0xFF); | |
370 | byte b = (byte) ((c) & 0xFF); | |
ec017591 MS |
371 | packet[len++] = (byte) 0; // alpha channel, unused but makes for 4-byte alignment |
372 | packet[len++] = (byte) r; | |
373 | packet[len++] = (byte) g; | |
374 | packet[len++] = (byte) b; | |
19ea62fd AK |
375 | } |
376 | } | |
ec017591 MS |
377 | |
378 | // for (LXPoint p : cube.points) { | |
379 | // int c = (p.index < 0) ? 0 : colors[p.index]; | |
380 | // byte r = (byte) ((c >> 16) & 0xFF); | |
381 | // byte g = (byte) ((c >> 8) & 0xFF); | |
382 | // byte b = (byte) ((c) & 0xFF); | |
383 | // packet[len++] = (byte) 0; // alpha channel, unused but makes for 4-byte alignment | |
384 | // packet[len++] = (byte) r; | |
385 | // packet[len++] = (byte) g; | |
386 | // packet[len++] = (byte) b; | |
387 | // } | |
19ea62fd | 388 | } |
ec017591 | 389 | } |
19ea62fd AK |
390 | // println("Packet number: " + packetNum); |
391 | sendPacket(frameNum, packetNum++); | |
392 | len = 0; | |
e73ef85d | 393 | } |
19ea62fd AK |
394 | // for (int index : points) { |
395 | // int c = (index < 0) ? 0 : colors[index]; | |
396 | // byte r = (byte) ((c >> 16) & 0xFF); | |
397 | // byte g = (byte) ((c >> 8) & 0xFF); | |
398 | // byte b = (byte) ((c) & 0xFF); | |
399 | // packet[len++] = 0; // alpha channel, unused but makes for 4-byte alignment | |
400 | // packet[len++] = r; | |
401 | // packet[len++] = g; | |
402 | // packet[len++] = b; | |
e73ef85d | 403 | |
19ea62fd AK |
404 | // // Flush once packet is full buffer size |
405 | // if (len >= packet.length) { | |
406 | // sendPacket(packetNum++); | |
407 | // len = 0; | |
408 | // } | |
409 | // } | |
410 | ||
411 | // // Flush any remaining data | |
412 | // if (len > 0) { | |
413 | // sendPacket(packetNum++); | |
414 | // } | |
e73ef85d MS |
415 | } |
416 | ||
e28f168c | 417 | |
19ea62fd AK |
418 | private void sendPacket(int frameNum, int packetNum) { |
419 | // println("Sending frame #" + frameNum + ", channel # " + packetNum); | |
e73ef85d | 420 | message.clearArguments(); |
19ea62fd AK |
421 | message.add(frameNum); |
422 | message.add(0xDEADBEEF); | |
e73ef85d | 423 | message.add(packetNum); |
19ea62fd | 424 | message.add(0xFEEDBEEF); |
f584b5eb | 425 | message.add(packet.length); |
e73ef85d | 426 | message.add(packet); |
19ea62fd AK |
427 | message.add(0xBEFFFFEB); |
428 | ||
e73ef85d | 429 | try { |
ec017591 MS |
430 | // OscP5.flush(message, address); // new DatagramSocket every time, no thanks |
431 | byte[] bytes = message.getBytes(); | |
432 | DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address.inetaddress(), PORT); | |
433 | socket.send(packet); | |
e73ef85d MS |
434 | } catch (Exception x) { |
435 | x.printStackTrace(); | |
436 | } | |
437 | } | |
438 | } |