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