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 : <Buffer>}.
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