rob@77
|
1 var min = require('osc-min');
|
rob@77
|
2 var dgram = require('dgram');
|
rob@77
|
3 var util = require('util');
|
rob@77
|
4 var events = require('events');
|
rob@77
|
5 var jspack = require('jspack').jspack;
|
rob@77
|
6
|
rob@77
|
7 ////////////////////
|
rob@77
|
8 // OSC Message
|
rob@77
|
9 ////////////////////
|
rob@77
|
10
|
rob@77
|
11
|
rob@77
|
12 function Message(address) {
|
rob@77
|
13 this.oscType = "message";
|
rob@77
|
14 this.address = address;
|
rob@77
|
15 this.args = [];
|
rob@77
|
16
|
rob@77
|
17 for (var i = 1; i < arguments.length; i++) {
|
rob@77
|
18 this.append(arguments[i]);
|
rob@77
|
19 }
|
rob@77
|
20 }
|
rob@77
|
21
|
rob@77
|
22 Message.prototype = {
|
rob@77
|
23 append: function (arg) {
|
rob@77
|
24 switch (typeof arg) {
|
rob@77
|
25 case 'object':
|
rob@77
|
26 if (arg.type) {
|
rob@77
|
27 this.args.push(arg);
|
rob@77
|
28 } else {
|
rob@77
|
29 throw new Error("don't know how to encode object " + arg)
|
rob@77
|
30 }
|
rob@77
|
31 break;
|
rob@77
|
32 case 'number':
|
rob@77
|
33 if (Math.floor(arg) == arg) {
|
rob@77
|
34 var argOut = new Argument('integer', arg);
|
rob@77
|
35 this.args.push(argOut);
|
rob@77
|
36 } else {
|
rob@77
|
37 var argOut = new Argument('float', arg);
|
rob@77
|
38 this.args.push(argOut);
|
rob@77
|
39 }
|
rob@77
|
40 break;
|
rob@77
|
41 case 'string':
|
rob@77
|
42 var argOut = new Argument('string', arg);
|
rob@77
|
43 this.args.push(argOut);
|
rob@77
|
44 break;
|
rob@77
|
45 default:
|
rob@77
|
46 throw new Error("don't know how to encode " + arg);
|
rob@77
|
47 }
|
rob@77
|
48 }
|
rob@77
|
49 }
|
rob@77
|
50
|
rob@77
|
51 exports.Message = Message;
|
rob@77
|
52
|
rob@77
|
53 function Argument(type, value){
|
rob@77
|
54 this.type = type;
|
rob@77
|
55 this.value = value;
|
rob@77
|
56 }
|
rob@77
|
57
|
rob@77
|
58 ////////////////////
|
rob@77
|
59 // OSC Client
|
rob@77
|
60 ////////////////////
|
rob@77
|
61
|
rob@77
|
62 var Client = function (host, port) {
|
rob@77
|
63 this.host = host;
|
rob@77
|
64 this.port = port;
|
rob@77
|
65 this._sock = dgram.createSocket('udp4');
|
rob@77
|
66 }
|
rob@77
|
67
|
rob@77
|
68 Client.prototype = {
|
rob@77
|
69 send: function (message) {
|
rob@77
|
70 switch (typeof message) {
|
rob@77
|
71 case 'object':
|
rob@77
|
72 var buf = min.toBuffer(message);
|
rob@77
|
73 this._sock.send(buf, 0, buf.length, this.port, this.host);
|
rob@77
|
74 break;
|
rob@77
|
75 case 'string':
|
rob@77
|
76 mes = new Message(arguments[0]);
|
rob@77
|
77 for (var i = 1; i < arguments.length; i++) {
|
rob@77
|
78 mes.append(arguments[i]);
|
rob@77
|
79 }
|
rob@77
|
80 var buf = min.toBuffer(mes);
|
rob@77
|
81 this._sock.send(buf, 0, buf.length, this.port, this.host);
|
rob@77
|
82 break;
|
rob@77
|
83 default:
|
rob@77
|
84 throw new Error("That Message Just Doesn't Seem Right");
|
rob@77
|
85 }
|
rob@77
|
86 }
|
rob@77
|
87 }
|
rob@77
|
88
|
rob@77
|
89 exports.Client = Client;
|
rob@77
|
90
|
rob@77
|
91 ////////////////////
|
rob@77
|
92 // OSC Message encoding and decoding functions
|
rob@77
|
93 ////////////////////
|
rob@77
|
94
|
rob@77
|
95 function ShortBuffer(type, buf, requiredLength)
|
rob@77
|
96 {
|
rob@77
|
97 this.type = "ShortBuffer";
|
rob@77
|
98 var message = "buffer [";
|
rob@77
|
99 for (var i = 0; i < buf.length; i++) {
|
rob@77
|
100 if (i) {
|
rob@77
|
101 message += ", ";
|
rob@77
|
102 }
|
rob@77
|
103 message += buf.charCodeAt(i);
|
rob@77
|
104 }
|
rob@77
|
105 message += "] too short for " + type + ", " + requiredLength + " bytes required";
|
rob@77
|
106 this.message = message;
|
rob@77
|
107 }
|
rob@77
|
108
|
rob@77
|
109 function TString (value) { this.value = value; }
|
rob@77
|
110 TString.prototype = {
|
rob@77
|
111 typetag: 's',
|
rob@77
|
112 decode: function (data) {
|
rob@77
|
113 var end = 0;
|
rob@77
|
114 while (data[end] && end < data.length) {
|
rob@77
|
115 end++;
|
rob@77
|
116 }
|
rob@77
|
117 if (end == data.length) {
|
rob@77
|
118 throw Error("OSC string not null terminated");
|
rob@77
|
119 }
|
rob@77
|
120 this.value = data.toString('ascii', 0, end);
|
rob@77
|
121 var nextData = parseInt(Math.ceil((end + 1) / 4.0) * 4);
|
rob@77
|
122 return data.slice(nextData);
|
rob@77
|
123 },
|
rob@77
|
124 encode: function (buf, pos) {
|
rob@77
|
125 var len = Math.ceil((this.value.length + 1) / 4.0) * 4;
|
rob@77
|
126 return jspack.PackTo('>' + len + 's', buf, pos, [ this.value ]);
|
rob@77
|
127 }
|
rob@77
|
128 }
|
rob@77
|
129 exports.TString = TString;
|
rob@77
|
130
|
rob@77
|
131 function TInt (value) { this.value = value; }
|
rob@77
|
132 TInt.prototype = {
|
rob@77
|
133 typetag: 'i',
|
rob@77
|
134 decode: function (data) {
|
rob@77
|
135 if (data.length < 4) {
|
rob@77
|
136 throw new ShortBuffer('int', data, 4);
|
rob@77
|
137 }
|
rob@77
|
138
|
rob@77
|
139 this.value = jspack.Unpack('>i', data.slice(0, 4))[0];
|
rob@77
|
140 return data.slice(4);
|
rob@77
|
141 },
|
rob@77
|
142 encode: function (buf, pos) {
|
rob@77
|
143 return jspack.PackTo('>i', buf, pos, [ this.value ]);
|
rob@77
|
144 }
|
rob@77
|
145 }
|
rob@77
|
146 exports.TInt = TInt;
|
rob@77
|
147
|
rob@77
|
148 function TTime (value) { this.value = value; }
|
rob@77
|
149 TTime.prototype = {
|
rob@77
|
150 typetag: 't',
|
rob@77
|
151 decode: function (data) {
|
rob@77
|
152 if (data.length < 8) {
|
rob@77
|
153 throw new ShortBuffer('time', data, 8);
|
rob@77
|
154 }
|
rob@77
|
155 var raw = jspack.Unpack('>LL', data.slice(0, 8));
|
rob@77
|
156 var secs = raw[0];
|
rob@77
|
157 var fracs = raw[1];
|
rob@77
|
158 this.value = secs + fracs / 4294967296;
|
rob@77
|
159 return data.slice(8);
|
rob@77
|
160 },
|
rob@77
|
161 encode: function (buf, pos) {
|
rob@77
|
162 return jspack.PackTo('>LL', buf, pos, this.value);
|
rob@77
|
163 }
|
rob@77
|
164 }
|
rob@77
|
165 exports.TTime = TTime;
|
rob@77
|
166
|
rob@77
|
167 function TFloat (value) { this.value = value; }
|
rob@77
|
168 TFloat.prototype = {
|
rob@77
|
169 typetag: 'f',
|
rob@77
|
170 decode: function (data) {
|
rob@77
|
171 if (data.length < 4) {
|
rob@77
|
172 throw new ShortBuffer('float', data, 4);
|
rob@77
|
173 }
|
rob@77
|
174
|
rob@77
|
175 this.value = jspack.Unpack('>f', data.slice(0, 4))[0];
|
rob@77
|
176 return data.slice(4);
|
rob@77
|
177 },
|
rob@77
|
178 encode: function (buf, pos) {
|
rob@77
|
179 return jspack.PackTo('>f', buf, pos, [ this.value ]);
|
rob@77
|
180 }
|
rob@77
|
181 }
|
rob@77
|
182 exports.TFloat = TFloat;
|
rob@77
|
183
|
rob@77
|
184 function TBlob (value) { this.value = value; }
|
rob@77
|
185 TBlob.prototype = {
|
rob@77
|
186 typetag: 'b',
|
rob@77
|
187 decode: function (data) {
|
rob@77
|
188 var length = jspack.Unpack('>i', data.slice(0, 4))[0];
|
rob@77
|
189 var nextData = parseInt(Math.ceil((length) / 4.0) * 4) + 4;
|
rob@77
|
190 this.value = data.slice(4, length + 4);
|
rob@77
|
191 return data.slice(nextData);
|
rob@77
|
192 },
|
rob@77
|
193 encode: function (buf, pos) {
|
rob@77
|
194 var len = Math.ceil((this.value.length) / 4.0) * 4;
|
rob@77
|
195 return jspack.PackTo('>i' + len + 's', buf, pos, [len, this.value]);
|
rob@77
|
196 }
|
rob@77
|
197 }
|
rob@77
|
198 exports.TBlob = TBlob;
|
rob@77
|
199
|
rob@77
|
200 function TDouble (value) { this.value = value; }
|
rob@77
|
201 TDouble.prototype = {
|
rob@77
|
202 typetag: 'd',
|
rob@77
|
203 decode: function (data) {
|
rob@77
|
204 if (data.length < 8) {
|
rob@77
|
205 throw new ShortBuffer('double', data, 8);
|
rob@77
|
206 }
|
rob@77
|
207 this.value = jspack.Unpack('>d', data.slice(0, 8))[0];
|
rob@77
|
208 return data.slice(8);
|
rob@77
|
209 },
|
rob@77
|
210 encode: function (buf, pos) {
|
rob@77
|
211 return jspack.PackTo('>d', buf, pos, [ this.value ]);
|
rob@77
|
212 }
|
rob@77
|
213 }
|
rob@77
|
214 exports.TDouble = TDouble;
|
rob@77
|
215
|
rob@77
|
216 // for each OSC type tag we use a specific constructor function to decode its respective data
|
rob@77
|
217 var tagToConstructor = { 'i': function () { return new TInt },
|
rob@77
|
218 'f': function () { return new TFloat },
|
rob@77
|
219 's': function () { return new TString },
|
rob@77
|
220 'b': function () { return new TBlob },
|
rob@77
|
221 'd': function () { return new TDouble } };
|
rob@77
|
222
|
rob@77
|
223 function decode (data) {
|
rob@77
|
224 // this stores the decoded data as an array
|
rob@77
|
225 var message = [];
|
rob@77
|
226
|
rob@77
|
227 // we start getting the <address> and <rest> of OSC msg /<address>\0<rest>\0<typetags>\0<data>
|
rob@77
|
228 var address = new TString;
|
rob@77
|
229 data = address.decode(data);
|
rob@77
|
230
|
rob@77
|
231 // Checking if we received a bundle (typical for TUIO/OSC)
|
rob@77
|
232 if (address.value == '#bundle') {
|
rob@77
|
233 var time = new TTime;
|
rob@77
|
234 data = time.decode(data);
|
rob@77
|
235
|
rob@77
|
236 message.push('#bundle');
|
rob@77
|
237 message.push(time.value);
|
rob@77
|
238
|
rob@77
|
239 var length, part;
|
rob@77
|
240 while(data.length > 0) {
|
rob@77
|
241 length = new TInt;
|
rob@77
|
242 data = length.decode(data);
|
rob@77
|
243
|
rob@77
|
244 part = data.slice(0, length.value);
|
rob@77
|
245 //message = message.concat(decode(part));
|
rob@77
|
246 message.push(decode(part));
|
rob@77
|
247
|
rob@77
|
248 data = data.slice(length.value, data.length);
|
rob@77
|
249 }
|
rob@77
|
250
|
rob@77
|
251 } else if (data.length > 0) {
|
rob@77
|
252 message.push(address.value);
|
rob@77
|
253
|
rob@77
|
254 // if we have rest, maybe we have some typetags... let see...
|
rob@77
|
255
|
rob@77
|
256 // now we advance on the old rest, getting <typetags>
|
rob@77
|
257 var typetags = new TString;
|
rob@77
|
258 data = typetags.decode(data);
|
rob@77
|
259 typetags = typetags.value;
|
rob@77
|
260 // so we start building our message list
|
rob@77
|
261 if (typetags[0] != ',') {
|
rob@77
|
262 throw "invalid type tag in incoming OSC message, must start with comma";
|
rob@77
|
263 }
|
rob@77
|
264 for (var i = 1; i < typetags.length; i++) {
|
rob@77
|
265 var constructor = tagToConstructor[typetags[i]];
|
rob@77
|
266 if (!constructor) {
|
rob@77
|
267 throw "Unsupported OSC type tag " + typetags[i] + " in incoming message";
|
rob@77
|
268 }
|
rob@77
|
269 var argument = constructor();
|
rob@77
|
270 data = argument.decode(data);
|
rob@77
|
271 message.push(argument.value);
|
rob@77
|
272 }
|
rob@77
|
273 }
|
rob@77
|
274
|
rob@77
|
275 return message;
|
rob@77
|
276 };
|
rob@77
|
277
|
rob@77
|
278
|
rob@77
|
279
|
rob@77
|
280 ////////////////////
|
rob@77
|
281 // OSC Server
|
rob@77
|
282 ////////////////////
|
rob@77
|
283
|
rob@77
|
284 var Server = function(port, host) {
|
rob@77
|
285 var _callbacks, server;
|
rob@77
|
286 events.EventEmitter.call(this);
|
rob@77
|
287 _callbacks = [];
|
rob@77
|
288 this.port = port;
|
rob@77
|
289 this.host = host;
|
rob@77
|
290 this._sock = dgram.createSocket('udp4');
|
rob@77
|
291 this._sock.bind(port);
|
rob@77
|
292 server = this;
|
rob@77
|
293 this._sock.on('message', function (msg, rinfo) {
|
rob@77
|
294 try {
|
rob@77
|
295 var decoded = decode(msg);
|
rob@77
|
296 // [<address>, <typetags>, <values>*]
|
rob@77
|
297 if (decoded) {
|
rob@77
|
298 server.emit('message', decoded, rinfo);
|
rob@77
|
299 server.emit(decoded[0], decoded, rinfo);
|
rob@77
|
300 }
|
rob@77
|
301 }
|
rob@77
|
302 catch (e) {
|
rob@77
|
303 console.log("can't decode incoming message: " + e.message);
|
rob@77
|
304 }
|
rob@77
|
305 });
|
rob@77
|
306 this.kill = function() {
|
rob@77
|
307 this._sock.close();
|
rob@77
|
308 };
|
rob@77
|
309 }
|
rob@77
|
310
|
rob@77
|
311 util.inherits(Server, events.EventEmitter);
|
rob@77
|
312
|
rob@77
|
313 exports.Server = Server;
|
rob@77
|
314
|
rob@77
|
315
|