l@271: # # osc-utilities.coffee l@271: # ## Intro l@271: # This file contains some lower-level utilities for OSC handling. l@271: # My guess is client code won't need this. If you do need this, you must l@271: # require coffee first, then write: l@271: # l@271: # require("coffee-script/register"); l@271: # osc-utils = require("osc/lib/osc-utilities"); l@271: # l@271: # See the comments in osc.coffee for more information about the structure of l@271: # the objects we're dealing with here. l@271: # l@271: l@271: # ## Dependencies l@271: # require the minimal binary packing utilities l@271: binpack = require "binpack" l@271: l@271: # ## Exported Functions l@271: l@271: # Utility for working with buffers. takes an array of buffers, l@271: # output one buffer with all of the array concatenated l@271: # l@271: # This is really only exported for TDD, but maybe it'll be useful l@271: # to someone else too. l@271: exports.concat = (buffers) -> l@271: if not IsArray buffers l@271: throw new Error "concat must take an array of buffers" l@271: l@271: for buffer in buffers l@271: if not Buffer.isBuffer(buffer) l@271: throw new Error "concat must take an array of buffers" l@271: l@271: sumLength = 0 l@271: sumLength += buffer.length for buffer in buffers l@271: l@271: destBuffer = new Buffer(sumLength) l@271: l@271: copyTo = 0 l@271: for buffer in buffers l@271: buffer.copy destBuffer, copyTo l@271: copyTo += buffer.length l@271: l@271: destBuffer l@271: l@271: # l@271: # Convert a javascript string into a node.js Buffer containing an OSC-String. l@271: # l@271: # str must not contain any \u0000 characters. l@271: # l@271: # `strict` is an optional boolean paramter that fails if the string is invalid l@271: # (i.e. contains a \u0000 character) l@271: exports.toOscString = (str, strict) -> l@271: if not (typeof str == "string") l@271: throw new Error "can't pack a non-string into an osc-string" l@271: l@271: # strip off any \u0000 characters. l@271: nullIndex = str.indexOf("\u0000") l@271: l@271: # if we're being strict, we can't allow strings with null characters l@271: if (nullIndex != -1 and strict) l@271: throw StrictError "Can't pack an osc-string that contains NULL characters" l@271: l@271: str = str[0...nullIndex] if nullIndex != -1 l@271: l@271: # osc-strings must have length divisible by 4 and end with at least one zero. l@271: for i in [0...(padding str)] l@271: str += "\u0000" l@271: l@271: # create a new buffer from the string. l@271: new Buffer(str) l@271: l@271: # l@271: # Try to split a buffer into a leading osc-string and the rest of the buffer, l@271: # with the following layout: l@271: # { string : "blah" rest : }. l@271: # l@271: # `strict`, as above, is an optional boolean parameter that defaults to false - l@271: # if it is true, then an invalid buffer will always return null. l@271: # l@271: exports.splitOscString = (buffer, strict) -> l@271: if not Buffer.isBuffer buffer l@271: throw StrictError "Can't split something that isn't a buffer" l@271: l@271: # extract the string l@271: rawStr = buffer.toString "utf8" l@271: nullIndex = rawStr.indexOf "\u0000" l@271: l@271: # the rest of the code doesn't apply if there's no null character. l@271: if nullIndex == -1 l@271: throw new Error "All osc-strings must contain a null character" if strict l@271: return {string:rawStr, rest:(new Buffer 0)} l@271: l@271: # extract the string. l@271: str = rawStr[0...nullIndex] l@271: l@271: # find the length of the string's buffer l@271: splitPoint = Buffer.byteLength(str) + padding(str) l@271: l@271: # in strict mode, don't succeed if there's not enough padding. l@271: if strict and splitPoint > buffer.length l@271: throw StrictError "Not enough padding for osc-string" l@271: l@271: # if we're in strict mode, check that all the padding is null l@271: if strict l@271: for i in [Buffer.byteLength(str)...splitPoint] l@271: if buffer[i] != 0 l@271: throw StrictError "Not enough or incorrect padding for osc-string" l@271: l@271: # return a split l@271: rest = buffer[splitPoint...(buffer.length)] l@271: l@271: {string: str, rest: rest} l@271: l@271: # This has similar semantics to splitOscString but works with integers instead. l@271: # bytes is the number of bytes in the integer, defaults to 4. l@271: exports.splitInteger = (buffer, type) -> l@271: type = "Int32" if not type? l@271: bytes = (binpack["pack" + type] 0).length l@271: l@271: if buffer.length < bytes l@271: throw new Error "buffer is not big enough for integer type" l@271: l@271: num = 0 l@271: l@271: # integers are stored in big endian format. l@271: value = binpack["unpack" + type] buffer[0...bytes], "big" l@271: l@271: rest = buffer[bytes...(buffer.length)] l@271: l@271: return {integer : value, rest : rest} l@271: l@271: # Split off an OSC timetag from buffer l@271: # returning {timetag: [seconds, fractionalSeconds], rest: restOfBuffer} l@271: exports.splitTimetag = (buffer) -> l@271: type = "Int32" l@271: bytes = (binpack["pack" + type] 0).length l@271: l@271: if buffer.length < (bytes * 2) l@271: throw new Error "buffer is not big enough to contain a timetag" l@271: l@271: # integers are stored in big endian format. l@271: a = 0 l@271: b = bytes l@271: seconds = binpack["unpack" + type] buffer[a...b], "big" l@271: c = bytes l@271: d = bytes + bytes l@271: fractional = binpack["unpack" + type] buffer[c...d], "big" l@271: rest = buffer[d...(buffer.length)] l@271: l@271: return {timetag: [seconds, fractional], rest: rest} l@271: l@271: UNIX_EPOCH = 2208988800 l@271: TWO_POW_32 = 4294967296 l@271: l@271: # Convert a JavaScript Date to a NTP timetag array. l@271: # Time zone of the Date object is respected, as the NTP l@271: # timetag uses UTC. l@271: exports.dateToTimetag = (date) -> l@271: return exports.timestampToTimetag(date.getTime() / 1000) l@271: l@271: # Convert a unix timestamp (seconds since jan 1 1970 UTC) l@271: # to NTP timestamp array l@271: exports.timestampToTimetag = (secs) -> l@271: wholeSecs = Math.floor(secs) l@271: fracSeconds = secs - wholeSecs l@271: return makeTimetag(wholeSecs, fracSeconds) l@271: l@271: # Convert a timetag to unix timestamp (seconds since unix epoch) l@271: exports.timetagToTimestamp = (timetag) -> l@271: seconds = timetag[0] + exports.ntpToFractionalSeconds(timetag[1]) l@271: return seconds - UNIX_EPOCH l@271: l@271: makeTimetag = (unixseconds, fracSeconds) -> l@271: # NTP epoch is 1900, JavaScript Date is unix 1970 l@271: ntpSecs = unixseconds + UNIX_EPOCH l@271: ntpFracs = Math.round(TWO_POW_32 * fracSeconds) l@271: return [ntpSecs, ntpFracs] l@271: l@271: # Convert NTP timestamp array to a JavaScript Date l@271: # in your systems local time zone. l@271: exports.timetagToDate = (timetag) -> l@271: [seconds, fractional] = timetag l@271: seconds = seconds - UNIX_EPOCH l@271: fracs = exports.ntpToFractionalSeconds(fractional) l@271: date = new Date() l@271: # Sets date to UTC/GMT l@271: date.setTime((seconds * 1000) + (fracs * 1000)) l@271: # Create a local timezone date l@271: dd = new Date() l@271: dd.setUTCFullYear(date.getUTCFullYear()) l@271: dd.setUTCMonth(date.getUTCMonth()) l@271: dd.setUTCDate(date.getUTCDate()) l@271: dd.setUTCHours(date.getUTCHours()) l@271: dd.setUTCMinutes(date.getUTCMinutes()) l@271: dd.setUTCSeconds(date.getUTCSeconds()) l@271: dd.setUTCMilliseconds(fracs * 1000) l@271: return dd l@271: l@271: # Make NTP timestamp array for relative future: now + seconds l@271: # Accuracy of 'now' limited to milliseconds but 'seconds' may be a full 32 bit float l@271: exports.deltaTimetag = (seconds, now) -> l@271: n = (now ? new Date()) / 1000 l@271: return exports.timestampToTimetag(n + seconds) l@271: l@271: # Convert 32 bit int for NTP fractional seconds l@271: # to a 32 bit float l@271: exports.ntpToFractionalSeconds = (fracSeconds) -> l@271: return parseFloat(fracSeconds) / TWO_POW_32 l@271: l@271: # Encodes a timetag of type null|Number|Array|Date l@271: # as a Buffer for adding to an OSC bundle. l@271: exports.toTimetagBuffer = (timetag) -> l@271: if typeof timetag is "number" l@271: timetag = exports.timestampToTimetag(timetag) l@271: else if typeof timetag is "object" and ("getTime" of timetag) l@271: # quacks like a Date l@271: timetag = exports.dateToTimetag(timetag) l@271: else if timetag.length != 2 l@271: throw new Error("Invalid timetag" + timetag) l@271: type = "Int32" l@271: high = binpack["pack" + type] timetag[0], "big" l@271: low = binpack["pack" + type] timetag[1], "big" l@271: return exports.concat([high, low]) l@271: l@271: exports.toIntegerBuffer = (number, type) -> l@271: type = "Int32" if not type? l@271: if typeof number isnt "number" l@271: throw new Error "cannot pack a non-number into an integer buffer" l@271: binpack["pack" + type] number, "big" l@271: l@271: # This mapping contains three fields for each type: l@271: # - representation : the javascript string representation of this type. l@271: # - split : a function to split a buffer into a decoded value and l@271: # the rest of the buffer. l@271: # - toArg : a function that takes the representation of the type and l@271: # outputs a buffer. l@271: oscTypeCodes = l@271: s : { l@271: representation : "string" l@271: split : (buffer, strict) -> l@271: # just pass it through to splitOscString l@271: split = exports.splitOscString buffer, strict l@271: {value : split.string, rest : split.rest} l@271: toArg : (value, strict) -> l@271: throw new Error "expected string" if typeof value isnt "string" l@271: exports.toOscString value, strict l@271: } l@271: i : { l@271: representation : "integer" l@271: split : (buffer, strict) -> l@271: split = exports.splitInteger buffer l@271: {value : split.integer, rest : split.rest} l@271: toArg : (value, strict) -> l@271: throw new Error "expected number" if typeof value isnt "number" l@271: exports.toIntegerBuffer value l@271: } l@271: t : { l@271: representation : "timetag" l@271: split : (buffer, strict) -> l@271: split = exports.splitTimetag buffer l@271: {value: split.timetag, rest: split.rest} l@271: toArg : (value, strict) -> l@271: exports.toTimetagBuffer value l@271: } l@271: f : { l@271: representation : "float" l@271: split : (buffer, strict) -> l@271: value : (binpack.unpackFloat32 buffer[0...4], "big") l@271: rest : buffer[4...(buffer.length)] l@271: toArg : (value, strict) -> l@271: throw new Error "expected number" if typeof value isnt "number" l@271: binpack.packFloat32 value, "big" l@271: } l@271: d : { l@271: representation : "double" l@271: split : (buffer, strict) -> l@271: value : (binpack.unpackFloat64 buffer[0...8], "big") l@271: rest : buffer[8...(buffer.length)] l@271: toArg : (value, strict) -> l@271: throw new Error "expected number" if typeof value isnt "number" l@271: binpack.packFloat64 value, "big" l@271: } l@271: b : { l@271: representation : "blob" l@271: split : (buffer, strict) -> l@271: # not much to do here, first grab an 4 byte int from the buffer l@271: {integer : length, rest : buffer} = exports.splitInteger buffer l@271: {value : buffer[0...length], rest : buffer[length...(buffer.length)]} l@271: toArg : (value, strict) -> l@271: throw new Error "expected node.js Buffer" if not Buffer.isBuffer value l@271: size = exports.toIntegerBuffer value.length l@271: exports.concat [size, value] l@271: } l@271: T : { l@271: representation : "true" l@271: split : (buffer, strict) -> l@271: rest : buffer l@271: value : true l@271: toArg : (value, strict) -> l@271: throw new Error "true must be true" if not value and strict l@271: new Buffer 0 l@271: } l@271: F : { l@271: representation : "false" l@271: split : (buffer, strict) -> l@271: rest : buffer l@271: value : false l@271: toArg : (value, strict) -> l@271: throw new Error "false must be false" if value and strict l@271: new Buffer 0 l@271: } l@271: N : { l@271: representation : "null" l@271: split : (buffer, strict) -> l@271: rest : buffer l@271: value : null l@271: toArg : (value, strict) -> l@271: throw new Error "null must be false" if value and strict l@271: new Buffer 0 l@271: } l@271: I : { l@271: representation : "bang" l@271: split : (buffer, strict) -> l@271: rest : buffer l@271: value : "bang" l@271: toArg : (value, strict) -> l@271: new Buffer 0 l@271: } l@271: l@271: # simple function that converts a type code into it's javascript l@271: # string representation. l@271: exports.oscTypeCodeToTypeString = (code) -> l@271: oscTypeCodes[code]?.representation l@271: l@271: # simple function that converts a javascript string representation l@271: # into its OSC type code. l@271: exports.typeStringToOscTypeCode = (rep) -> l@271: for own code, {representation : str} of oscTypeCodes l@271: return code if str is rep l@271: return null l@271: l@271: exports.argToTypeCode = (arg, strict) -> l@271: # if there's an explicit type annotation, back-translate that. l@271: if arg?.type? and l@271: (typeof arg.type is 'string') and l@271: (code = exports.typeStringToOscTypeCode arg.type)? l@271: return code l@271: l@271: value = if arg?.value? then arg.value else arg l@271: l@271: # now, we try to guess the type. l@271: throw new Error 'Argument has no value' if strict and not value? l@271: l@271: # if it's a string, use 's' l@271: if typeof value is 'string' l@271: return 's' l@271: l@271: # if it's a number, use 'f' by default. l@271: if typeof value is 'number' l@271: return 'f' l@271: l@271: # if it's a buffer, use 'b' l@271: if Buffer.isBuffer(value) l@271: return 'b' l@271: l@271: #### These are 1.1 specific types. l@271: l@271: # if it's a boolean, use 'T' or 'F' l@271: if typeof value is 'boolean' l@271: if value then return 'T' else return 'F' l@271: l@271: # if it's null, use 'N' l@271: if value is null l@271: return 'N' l@271: l@271: throw new Error "I don't know what type this is supposed to be." l@271: l@271: # Splits out an argument from buffer. Same thing as splitOscString but l@271: # works for all argument types. l@271: exports.splitOscArgument = (buffer, type, strict) -> l@271: osctype = exports.typeStringToOscTypeCode type l@271: if osctype? l@271: oscTypeCodes[osctype].split buffer, strict l@271: else l@271: throw new Error "I don't understand how I'm supposed to unpack #{type}" l@271: l@271: # Create a buffer with the given javascript type l@271: exports.toOscArgument = (value, type, strict) -> l@271: osctype = exports.typeStringToOscTypeCode type l@271: if osctype? l@271: oscTypeCodes[osctype].toArg value, strict l@271: else l@271: throw new Error "I don't know how to pack #{type}" l@271: l@271: # l@271: # translates an OSC message into a javascript representation. l@271: # l@271: exports.fromOscMessage = (buffer, strict) -> l@271: # break off the address l@271: { string : address, rest : buffer} = exports.splitOscString buffer, strict l@271: l@271: # technically, addresses have to start with '/'. l@271: if strict and address[0] isnt '/' l@271: throw StrictError 'addresses must start with /' l@271: l@271: # if there's no type string, this is technically illegal, but l@271: # the specification says we should accept this until all l@271: # implementations that send message without a type string are fixed. l@271: # this will never happen, so we should accept this, even in l@271: # strict mode. l@271: return {address : address, args : []} if not buffer.length l@271: l@271: # if there's more data but no type string, we can't parse the arguments. l@271: {string : types, rest : buffer} = exports.splitOscString buffer, strict l@271: l@271: # if the first letter isn't a ',' this isn't a valid type so we can't l@271: # parse the arguments. l@271: if types[0] isnt ',' l@271: throw StrictError 'Argument lists must begin with ,' if strict l@271: return {address : address, args : []} l@271: l@271: # we don't need the comma anymore l@271: types = types[1..(types.length)] l@271: l@271: args = [] l@271: l@271: # we use this to build up array arguments. l@271: # arrayStack[-1] is always the currently contructing l@271: # array. l@271: arrayStack = [args] l@271: l@271: # grab each argument. l@271: for type in types l@271: # special case: we're beginning construction of an array. l@271: if type is '[' l@271: arrayStack.push([]) l@271: continue l@271: l@271: # special case: we've just finished constructing an array. l@271: if type is ']' l@271: if arrayStack.length <= 1 l@271: throw new StrictError "Mismatched ']' character." if strict l@271: else l@271: built = arrayStack.pop() l@271: arrayStack[arrayStack.length-1].push( l@271: type: 'array' l@271: value: built l@271: ) l@271: continue l@271: l@271: # by the standard, we have to ignore the whole message l@271: # if we don't understand an argument l@271: typeString = exports.oscTypeCodeToTypeString type l@271: if not typeString? l@271: throw new Error "I don't understand the argument code #{type}" l@271: l@271: arg = exports.splitOscArgument buffer, typeString, strict l@271: l@271: # consume the argument from the buffer l@271: buffer = arg.rest if arg? l@271: l@271: # add it to the list. l@271: arrayStack[arrayStack.length-1].push( l@271: type : typeString l@271: value : arg?.value l@271: ) l@271: l@271: if arrayStack.length isnt 1 and strict l@271: throw new StrictError "Mismatched '[' character" l@271: {address : address, args : args, oscType : "message"} l@271: l@271: # l@271: # Try to parse an OSC bundle into a javascript object. l@271: # l@271: exports.fromOscBundle = (buffer, strict) -> l@271: # break off the bundletag l@271: { string : bundleTag, rest : buffer} = exports.splitOscString buffer, strict l@271: l@271: # bundles have to start with "#bundle". l@271: if bundleTag isnt "\#bundle" l@271: throw new Error "osc-bundles must begin with \#bundle" l@271: l@271: # grab the 8 byte timetag l@271: {timetag: timetag, rest: buffer} = exports.splitTimetag buffer l@271: l@271: # convert each element. l@271: convertedElems = mapBundleList buffer, (buffer) -> l@271: exports.fromOscPacket buffer, strict l@271: l@271: return {timetag : timetag, elements : convertedElems, oscType : "bundle"} l@271: l@271: # l@271: # convert the buffer into a bundle or a message, depending on the first string l@271: # l@271: exports.fromOscPacket = (buffer, strict) -> l@271: if isOscBundleBuffer buffer, strict l@271: exports.fromOscBundle buffer, strict l@271: else l@271: exports.fromOscMessage buffer, strict l@271: l@271: # helper - is it an argument that represents an array? l@271: getArrayArg = (arg) -> l@271: if IsArray arg l@271: arg l@271: else if (arg?.type is "array") and (IsArray arg?.value) l@271: arg.value l@271: else if arg? and (not arg.type?) and (IsArray arg.value) l@271: arg.value l@271: else l@271: null l@271: l@271: # helper - converts an argument list into a pair of a type string and a l@271: # data buffer l@271: # argList must be an array!!! l@271: toOscTypeAndArgs = (argList, strict) -> l@271: osctype = "" l@271: oscargs = [] l@271: for arg in argList l@271: if (getArrayArg arg)? l@271: [thisType, thisArgs] = toOscTypeAndArgs (getArrayArg arg), strict l@271: osctype += "[" + thisType + "]" l@271: oscargs = oscargs.concat thisArgs l@271: continue l@271: typeCode = exports.argToTypeCode arg, strict l@271: if typeCode? l@271: value = arg?.value l@271: if value is undefined l@271: value = arg l@271: buff = exports.toOscArgument value, l@271: (exports.oscTypeCodeToTypeString typeCode), strict l@271: if buff? l@271: oscargs.push buff l@271: osctype += typeCode l@271: [osctype, oscargs] l@271: l@271: # l@271: # convert a javascript format message into an osc buffer l@271: # l@271: exports.toOscMessage = (message, strict) -> l@271: # the message must have addresses and arguments. l@271: address = if message?.address? then message.address else message l@271: if typeof address isnt "string" l@271: throw new Error "message must contain an address" l@271: l@271: args = message?.args l@271: l@271: if args is undefined l@271: args = [] l@271: l@271: # pack single args l@271: if not IsArray args l@271: old_arg = args l@271: args = [] l@271: args[0] = old_arg l@271: l@271: oscaddr = exports.toOscString address, strict l@271: [osctype, oscargs] = toOscTypeAndArgs args, strict l@271: osctype = "," + osctype l@271: l@271: # bundle everything together. l@271: allArgs = exports.concat oscargs l@271: l@271: # convert the type tag into an oscString. l@271: osctype = exports.toOscString osctype l@271: l@271: exports.concat [oscaddr, osctype, allArgs] l@271: l@271: # l@271: # convert a javascript format bundle into an osc buffer l@271: # l@271: exports.toOscBundle = (bundle, strict) -> l@271: # the bundle must have timetag and elements. l@271: if strict and not bundle?.timetag? l@271: throw StrictError "bundles must have timetags." l@271: timetag = bundle?.timetag ? new Date() l@271: elements = bundle?.elements ? [] l@271: if not IsArray elements l@271: elemstr = elements l@271: elements = [] l@271: elements.push elemstr l@271: l@271: oscBundleTag = exports.toOscString "\#bundle" l@271: oscTimeTag = exports.toTimetagBuffer timetag l@271: l@271: oscElems = [] l@271: for elem in elements l@271: try l@271: # try to convert this sub-element into a buffer l@271: buff = exports.toOscPacket elem, strict l@271: l@271: # okay, pack in the size. l@271: size = exports.toIntegerBuffer buff.length l@271: oscElems.push exports.concat [size, buff] l@271: catch e l@271: null l@271: l@271: allElems = exports.concat oscElems l@271: exports.concat [oscBundleTag, oscTimeTag, allElems] l@271: l@271: # convert a javascript format bundle or message into a buffer l@271: exports.toOscPacket = (bundleOrMessage, strict) -> l@271: # first, determine whether or not this is a bundle. l@271: if bundleOrMessage?.oscType? l@271: if bundleOrMessage.oscType is "bundle" l@271: return exports.toOscBundle bundleOrMessage, strict l@271: return exports.toOscMessage bundleOrMessage, strict l@271: l@271: # bundles have "timetags" and "elements" l@271: if bundleOrMessage?.timetag? or bundleOrMessage?.elements? l@271: return exports.toOscBundle bundleOrMessage, strict l@271: l@271: exports.toOscMessage bundleOrMessage, strict l@271: l@271: # l@271: # Helper function for transforming all messages in a bundle with a given message l@271: # transform. l@271: # l@271: exports.applyMessageTranformerToBundle = (transform) -> (buffer) -> l@271: l@271: # parse out the bundle-id and the tag, we don't want to change these l@271: { string, rest : buffer} = exports.splitOscString buffer l@271: l@271: # bundles have to start with "#bundle". l@271: if string isnt "\#bundle" l@271: throw new Error "osc-bundles must begin with \#bundle" l@271: l@271: bundleTagBuffer = exports.toOscString string l@271: l@271: # we know that the timetag is 8 bytes, we don't want to mess with it, l@271: # so grab it as a buffer. There is some subtle loss of precision with l@271: # the round trip from int64 to float64. l@271: timetagBuffer = buffer[0...8] l@271: buffer = buffer[8...buffer.length] l@271: l@271: # convert each element. l@271: elems = mapBundleList buffer, (buffer) -> l@271: exports.applyTransform( l@271: buffer, l@271: transform, l@271: exports.applyMessageTranformerToBundle transform l@271: ) l@271: l@271: totalLength = bundleTagBuffer.length + timetagBuffer.length l@271: totalLength += 4 + elem.length for elem in elems l@271: l@271: # okay, now we have to reconcatenate everything. l@271: outBuffer = new Buffer totalLength l@271: bundleTagBuffer.copy outBuffer, 0 l@271: timetagBuffer.copy outBuffer, bundleTagBuffer.length l@271: copyIndex = bundleTagBuffer.length + timetagBuffer.length l@271: for elem in elems l@271: lengthBuff = exports.toIntegerBuffer elem.length l@271: lengthBuff.copy outBuffer, copyIndex l@271: copyIndex += 4 l@271: elem.copy outBuffer, copyIndex l@271: copyIndex += elem.length l@271: outBuffer l@271: l@271: # l@271: # Applies a transformation function (that is, a function from buffers l@271: # to buffers) to each element of given osc-bundle or message. l@271: # l@271: # `buffer` is the buffer to transform, which must be a buffer of a full packet. l@271: # `messageTransform` is function from message buffers to message buffers l@271: # `bundleTransform` is an optional parameter for functions from bundle buffers l@271: # to bundle buffers. l@271: # if `bundleTransform` is not set, it defaults to just applying the l@271: # `messageTransform` to each message in the bundle. l@271: # l@271: exports.applyTransform = (buffer, mTransform, bundleTransform) -> l@271: if not bundleTransform? l@271: bundleTransform = exports.applyMessageTranformerToBundle mTransform l@271: l@271: if isOscBundleBuffer buffer l@271: bundleTransform buffer l@271: else l@271: mTransform buffer l@271: l@271: # Converts a javascript function from string to string to a function l@271: # from message buffer to message buffer, applying the function to the l@271: # parsed strings. l@271: # l@271: # We pre-curry this because we expect to use this with `applyMessageTransform` l@271: # above l@271: # l@271: exports.addressTransform = (transform) -> (buffer) -> l@271: # parse out the address l@271: {string, rest} = exports.splitOscString buffer l@271: l@271: # apply the function l@271: string = transform string l@271: l@271: # re-concatenate l@271: exports.concat [ l@271: exports.toOscString string l@271: rest l@271: ] l@271: l@271: # l@271: # Take a function that transform a javascript _OSC Message_ and l@271: # convert it to a function that transforms osc-buffers. l@271: # l@271: exports.messageTransform = (transform) -> (buffer) -> l@271: message = exports.fromOscMessage buffer l@271: exports.toOscMessage transform message l@271: l@271: ## Private utilities l@271: l@271: # l@271: # is it an array? l@271: # l@271: IsArray = Array.isArray l@271: l@271: # l@271: # An error that only throws when we're in strict mode. l@271: # l@271: StrictError = (str) -> l@271: new Error "Strict Error: " + str l@271: l@271: # this private utility finds the amount of padding for a given string. l@271: padding = (str) -> l@271: bufflength = Buffer.byteLength(str) l@271: 4 - (bufflength % 4) l@271: l@271: # l@271: # Internal function to check if this is a message or bundle. l@271: # l@271: isOscBundleBuffer = (buffer, strict) -> l@271: # both formats begin with strings, so we should just grab the front but not l@271: # consume it. l@271: {string} = exports.splitOscString buffer, strict l@271: l@271: return string is "\#bundle" l@271: l@271: # l@271: # Does something for each element in an array of osc-message-or-bundles, l@271: # each prefixed by a length (such as appears in osc-messages), then l@271: # return the result as an array. l@271: # l@271: # This is not exported because it doesn't validate the format and it's l@271: # not really a generally useful function. l@271: # l@271: # If a function throws on an element, we discard that element in the map l@271: # but we don't give up completely. l@271: # l@271: mapBundleList = (buffer, func) -> l@271: elems = while buffer.length l@271: # the length of the element is stored in an integer l@271: {integer : size, rest : buffer} = exports.splitInteger buffer l@271: l@271: # if the size is bigger than the packet, something's messed up, so give up. l@271: if size > buffer.length l@271: throw new Error( l@271: "Invalid bundle list: size of element is bigger than buffer") l@271: l@271: thisElemBuffer = buffer[0...size] l@271: l@271: # move the buffer to after the element we're just parsing. l@271: buffer = buffer[size...buffer.length] l@271: l@271: # record this element l@271: try l@271: func thisElemBuffer l@271: catch e l@271: null l@271: l@271: # remove all null from elements l@271: nonNullElems = [] l@271: for elem in elems l@271: (nonNullElems.push elem) if elem? l@271: l@271: nonNullElems