312 lines
8.7 KiB
JavaScript
312 lines
8.7 KiB
JavaScript
var min = require('osc-min');
|
|
var dgram = require('dgram');
|
|
var util = require('util');
|
|
var events = require('events');
|
|
var jspack = require('jspack').jspack;
|
|
|
|
////////////////////
|
|
// OSC Message
|
|
////////////////////
|
|
|
|
|
|
function Message(address) {
|
|
this.oscType = "message";
|
|
this.address = address;
|
|
this.args = [];
|
|
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
this.append(arguments[i]);
|
|
}
|
|
}
|
|
|
|
Message.prototype = {
|
|
append: function (arg) {
|
|
switch (typeof arg) {
|
|
case 'object':
|
|
if (arg.type) {
|
|
this.args.push(arg);
|
|
} else {
|
|
throw new Error("don't know how to encode object " + arg)
|
|
}
|
|
break;
|
|
case 'number':
|
|
if (Math.floor(arg) == arg) {
|
|
var argOut = new Argument('integer', arg);
|
|
this.args.push(argOut);
|
|
} else {
|
|
var argOut = new Argument('float', arg);
|
|
this.args.push(argOut);
|
|
}
|
|
break;
|
|
case 'string':
|
|
var argOut = new Argument('string', arg);
|
|
this.args.push(argOut);
|
|
break;
|
|
default:
|
|
throw new Error("don't know how to encode " + arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.Message = Message;
|
|
|
|
function Argument(type, value){
|
|
this.type = type;
|
|
this.value = value;
|
|
}
|
|
|
|
////////////////////
|
|
// OSC Client
|
|
////////////////////
|
|
|
|
var Client = function (host, port) {
|
|
this.host = host;
|
|
this.port = port;
|
|
this._sock = dgram.createSocket('udp4');
|
|
}
|
|
|
|
Client.prototype = {
|
|
send: function (message) {
|
|
switch (typeof message) {
|
|
case 'object':
|
|
var buf = min.toBuffer(message);
|
|
this._sock.send(buf, 0, buf.length, this.port, this.host);
|
|
break;
|
|
case 'string':
|
|
mes = new Message(arguments[0]);
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
mes.append(arguments[i]);
|
|
}
|
|
var buf = min.toBuffer(mes);
|
|
this._sock.send(buf, 0, buf.length, this.port, this.host);
|
|
break;
|
|
default:
|
|
throw new Error("That Message Just Doesn't Seem Right");
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.Client = Client;
|
|
|
|
////////////////////
|
|
// OSC Message encoding and decoding functions
|
|
////////////////////
|
|
|
|
function ShortBuffer(type, buf, requiredLength)
|
|
{
|
|
this.type = "ShortBuffer";
|
|
var message = "buffer [";
|
|
for (var i = 0; i < buf.length; i++) {
|
|
if (i) {
|
|
message += ", ";
|
|
}
|
|
message += buf.charCodeAt(i);
|
|
}
|
|
message += "] too short for " + type + ", " + requiredLength + " bytes required";
|
|
this.message = message;
|
|
}
|
|
|
|
function TString (value) { this.value = value; }
|
|
TString.prototype = {
|
|
typetag: 's',
|
|
decode: function (data) {
|
|
var end = 0;
|
|
while (data[end] && end < data.length) {
|
|
end++;
|
|
}
|
|
if (end == data.length) {
|
|
throw Error("OSC string not null terminated");
|
|
}
|
|
this.value = data.toString('ascii', 0, end);
|
|
var nextData = parseInt(Math.ceil((end + 1) / 4.0) * 4);
|
|
return data.slice(nextData);
|
|
},
|
|
encode: function (buf, pos) {
|
|
var len = Math.ceil((this.value.length + 1) / 4.0) * 4;
|
|
return jspack.PackTo('>' + len + 's', buf, pos, [ this.value ]);
|
|
}
|
|
}
|
|
exports.TString = TString;
|
|
|
|
function TInt (value) { this.value = value; }
|
|
TInt.prototype = {
|
|
typetag: 'i',
|
|
decode: function (data) {
|
|
if (data.length < 4) {
|
|
throw new ShortBuffer('int', data, 4);
|
|
}
|
|
|
|
this.value = jspack.Unpack('>i', data.slice(0, 4))[0];
|
|
return data.slice(4);
|
|
},
|
|
encode: function (buf, pos) {
|
|
return jspack.PackTo('>i', buf, pos, [ this.value ]);
|
|
}
|
|
}
|
|
exports.TInt = TInt;
|
|
|
|
function TTime (value) { this.value = value; }
|
|
TTime.prototype = {
|
|
typetag: 't',
|
|
decode: function (data) {
|
|
if (data.length < 8) {
|
|
throw new ShortBuffer('time', data, 8);
|
|
}
|
|
var raw = jspack.Unpack('>LL', data.slice(0, 8));
|
|
var secs = raw[0];
|
|
var fracs = raw[1];
|
|
this.value = secs + fracs / 4294967296;
|
|
return data.slice(8);
|
|
},
|
|
encode: function (buf, pos) {
|
|
return jspack.PackTo('>LL', buf, pos, this.value);
|
|
}
|
|
}
|
|
exports.TTime = TTime;
|
|
|
|
function TFloat (value) { this.value = value; }
|
|
TFloat.prototype = {
|
|
typetag: 'f',
|
|
decode: function (data) {
|
|
if (data.length < 4) {
|
|
throw new ShortBuffer('float', data, 4);
|
|
}
|
|
|
|
this.value = jspack.Unpack('>f', data.slice(0, 4))[0];
|
|
return data.slice(4);
|
|
},
|
|
encode: function (buf, pos) {
|
|
return jspack.PackTo('>f', buf, pos, [ this.value ]);
|
|
}
|
|
}
|
|
exports.TFloat = TFloat;
|
|
|
|
function TBlob (value) { this.value = value; }
|
|
TBlob.prototype = {
|
|
typetag: 'b',
|
|
decode: function (data) {
|
|
var length = jspack.Unpack('>i', data.slice(0, 4))[0];
|
|
var nextData = parseInt(Math.ceil((length) / 4.0) * 4) + 4;
|
|
this.value = data.slice(4, length + 4);
|
|
return data.slice(nextData);
|
|
},
|
|
encode: function (buf, pos) {
|
|
var len = Math.ceil((this.value.length) / 4.0) * 4;
|
|
return jspack.PackTo('>i' + len + 's', buf, pos, [len, this.value]);
|
|
}
|
|
}
|
|
exports.TBlob = TBlob;
|
|
|
|
function TDouble (value) { this.value = value; }
|
|
TDouble.prototype = {
|
|
typetag: 'd',
|
|
decode: function (data) {
|
|
if (data.length < 8) {
|
|
throw new ShortBuffer('double', data, 8);
|
|
}
|
|
this.value = jspack.Unpack('>d', data.slice(0, 8))[0];
|
|
return data.slice(8);
|
|
},
|
|
encode: function (buf, pos) {
|
|
return jspack.PackTo('>d', buf, pos, [ this.value ]);
|
|
}
|
|
}
|
|
exports.TDouble = TDouble;
|
|
|
|
// for each OSC type tag we use a specific constructor function to decode its respective data
|
|
var tagToConstructor = { 'i': function () { return new TInt },
|
|
'f': function () { return new TFloat },
|
|
's': function () { return new TString },
|
|
'b': function () { return new TBlob },
|
|
'd': function () { return new TDouble } };
|
|
|
|
function decode (data) {
|
|
// this stores the decoded data as an array
|
|
var message = [];
|
|
|
|
// we start getting the <address> and <rest> of OSC msg /<address>\0<rest>\0<typetags>\0<data>
|
|
var address = new TString;
|
|
data = address.decode(data);
|
|
|
|
// Checking if we received a bundle (typical for TUIO/OSC)
|
|
if (address.value == '#bundle') {
|
|
var time = new TTime;
|
|
data = time.decode(data);
|
|
|
|
message.push('#bundle');
|
|
message.push(time.value);
|
|
|
|
var length, part;
|
|
while(data.length > 0) {
|
|
length = new TInt;
|
|
data = length.decode(data);
|
|
|
|
part = data.slice(0, length.value);
|
|
//message = message.concat(decode(part));
|
|
message.push(decode(part));
|
|
|
|
data = data.slice(length.value, data.length);
|
|
}
|
|
|
|
} else if (data.length > 0) {
|
|
message.push(address.value);
|
|
|
|
// if we have rest, maybe we have some typetags... let see...
|
|
|
|
// now we advance on the old rest, getting <typetags>
|
|
var typetags = new TString;
|
|
data = typetags.decode(data);
|
|
typetags = typetags.value;
|
|
// so we start building our message list
|
|
if (typetags[0] != ',') {
|
|
throw "invalid type tag in incoming OSC message, must start with comma";
|
|
}
|
|
for (var i = 1; i < typetags.length; i++) {
|
|
var constructor = tagToConstructor[typetags[i]];
|
|
if (!constructor) {
|
|
throw "Unsupported OSC type tag " + typetags[i] + " in incoming message";
|
|
}
|
|
var argument = constructor();
|
|
data = argument.decode(data);
|
|
message.push(argument.value);
|
|
}
|
|
}
|
|
|
|
return message;
|
|
};
|
|
|
|
|
|
|
|
////////////////////
|
|
// OSC Server
|
|
////////////////////
|
|
|
|
var Server = function(port, host) {
|
|
events.EventEmitter.call(this);
|
|
var _callbacks = [];
|
|
this.port = port;
|
|
this.host = host;
|
|
this._sock = dgram.createSocket('udp4');
|
|
this._sock.bind(port);
|
|
var server = this;
|
|
this._sock.on('message', function (msg, rinfo) {
|
|
try {
|
|
var decoded = decode(msg);
|
|
// [<address>, <typetags>, <values>*]
|
|
if (decoded) {
|
|
server.emit('message', decoded, rinfo);
|
|
server.emit(decoded[0], decoded, rinfo);
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log("can't decode incoming message: " + e.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
util.inherits(Server, events.EventEmitter);
|
|
|
|
exports.Server = Server;
|
|
|
|
|