Switch to threaded mode by default, run engine at 120FPS
[SugarCubes.git] / Grizzly.pde
1 /**
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//H
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
11 * If you are an artist, you may ignore this file! It contains
12 * the code to drive grizzly board outputs.
13 */
14 import java.net.*;
15 GrizzlyOutput[] buildGrizzlies() throws SocketException, UnknownHostException {
16 return new GrizzlyOutput[] {
17 new GrizzlyOutput(lx, "192.168.88.100", 6, 5, 6, 7, 7, 8, 1, 2, 4, 3, 11, 10, 9, 9, 12, 13),
18 new GrizzlyOutput(lx, "192.168.88.101", 25, 23, 24, 43, 45, 44, 1, 1, 1, 1, 1, 41, 42, 21, 20, 22),
19 new GrizzlyOutput(lx, "192.168.88.104", 26, 28, 27, 19, 18, 17, 1, 1, 18, 19, 15, 16, 14, 29, 30, 31),
20 new GrizzlyOutput(lx, "192.168.88.105", 1, 1, 1, 39, 38, 40, 34, 35, 33, 32, 37, 37, 1, 1, 1, 1),
21 };
22 }
23
24 /**
25 * Grizzly Output, sends packets to one grizzly board with a fixed IP and a number
26 * of channels.
27 */
28 class GrizzlyOutput extends LXDatagramOutput {
29
30 public final String ipAddress;
31
32 private int frameNumber = 0;
33
34 public GrizzlyOutput(LX lx, String ipAddress, int ... cubeIndices) throws UnknownHostException, SocketException {
35 super(lx);
36 this.ipAddress = ipAddress;
37 int channelNum = 0;
38 for (int rawCubeIndex : cubeIndices) {
39 if (rawCubeIndex > 0) {
40 Cube cube = model.getCubeByRawIndex(rawCubeIndex);
41 addDatagram(new GrizzlyDatagram(this, channelNum, cube).setAddress(ipAddress));
42 }
43 ++channelNum;
44 }
45 this.enabled.setValue(false);
46 }
47
48 protected void beforeSend(int[] colors) {
49 ++frameNumber;
50 }
51
52 public int getFrameNumber() {
53 return this.frameNumber;
54 }
55 }
56
57 /**
58 * Datagram to a Grizzlyboard. A simple fixed OSC packet.
59 */
60 static class GrizzlyDatagram extends LXDatagram {
61
62 private static byte[] oscString(String s) {
63 int len = s.length();
64 int padding = (4 - ((len + 1) % 4)) % 4;
65 byte[] bytes = new byte[len + 1 + padding];
66 System.arraycopy(s.getBytes(), 0, bytes, 0, len);
67 for (int i = len; i < bytes.length; ++i) {
68 bytes[i] = 0;
69 }
70 return bytes;
71 }
72
73 private static int oscIntCopy(int i, byte[] buffer, int pos) {
74 buffer[pos] = (byte) ((i >> 24) & 0xff);
75 buffer[pos + 1] = (byte) ((i >> 16) & 0xff);
76 buffer[pos + 2] = (byte) ((i >> 8) & 0xff);
77 buffer[pos + 3] = (byte) (i & 0xff);
78 return 4;
79 }
80
81 private final static int[] STRIP_ORDERING = new int[] { 9, 8, 11, 5, 4, 7, 6, 10, 14, 2, 1, 0, 3, 13, 12, 15 };
82
83 private static int[] cubePointIndices(Cube cube) {
84 int[] pointIndices = new int[Cube.POINTS_PER_CUBE - 2];
85 int pi = 0;
86 for (int stripIndex : STRIP_ORDERING) {
87 Strip strip = cube.strips.get(stripIndex);
88 int stripLen = ((stripIndex == 9) || (stripIndex == 15)) ? 15 : 16;
89 for (int i = stripLen-1; i >= 0; --i) {
90 pointIndices[pi++] = strip.points.get(i).index;
91 }
92 }
93 return pointIndices;
94 }
95
96 private final static byte[] OSC_ADDRESS = oscString("/shady/pointbuffer");
97 private final static byte[] OSC_TYPETAG = oscString(",iiiiibi");
98
99 private final static int HEADER_LENGTH = OSC_ADDRESS.length + OSC_TYPETAG.length + 24;
100 private final static int FOOTER_LENGTH = 4;
101
102 private final static int OSC_PORT = 779;
103
104 private GrizzlyOutput output;
105
106 private int[] pointIndices;
107
108 private final int frameNumberPos;
109
110 private final int dataPos;
111
112 public GrizzlyDatagram(GrizzlyOutput output, int channelNum, Cube cube) {
113 this(output, channelNum, cubePointIndices(cube));
114 }
115
116 public GrizzlyDatagram(GrizzlyOutput output, int channelNum, int[] pointIndices) {
117 super(HEADER_LENGTH + 4*pointIndices.length + FOOTER_LENGTH);
118 setPort(OSC_PORT);
119
120 this.output = output;
121 this.pointIndices = pointIndices;
122 int dataLength = 4*pointIndices.length;
123
124 int pos = 0;
125
126 // OSC address
127 System.arraycopy(OSC_ADDRESS, 0, this.buffer, pos, OSC_ADDRESS.length);
128 pos += OSC_ADDRESS.length;
129
130 // OSC typetag
131 System.arraycopy(OSC_TYPETAG, 0, this.buffer, pos, OSC_TYPETAG.length);
132 pos += OSC_TYPETAG.length;
133 this.frameNumberPos = pos;
134 pos += oscIntCopy(0, this.buffer, pos); // placeholder for frame number
135 pos += oscIntCopy(0xDEADBEEF, this.buffer, pos);
136 pos += oscIntCopy(channelNum, this.buffer, pos);
137 pos += oscIntCopy(0xFEEDBEEF, this.buffer, pos);
138 pos += oscIntCopy(dataLength, this.buffer, pos);
139 pos += oscIntCopy(dataLength, this.buffer, pos);
140 this.dataPos = pos;
141
142 // end header
143 oscIntCopy(0xBEFFFFEB, this.buffer, this.buffer.length - 4);
144 }
145
146 void onSend(int[] colors) {
147 oscIntCopy(this.output.getFrameNumber(), this.buffer, frameNumberPos);
148 int dataIndex = this.dataPos;
149 for (int index : this.pointIndices) {
150 color c = (index >= 0) ? colors[index] : 0;
151 this.buffer[dataIndex] = (byte) 0; // unused, alpha
152 this.buffer[dataIndex + 1] = (byte) ((c >> 16) & 0xff); // r
153 this.buffer[dataIndex + 2] = (byte) ((c >> 8) & 0xff); // g
154 this.buffer[dataIndex + 3] = (byte) (c & 0xff); // b
155 dataIndex += 4;
156 }
157 }
158 }