annotate resources/osc/node_modules/osc-min/lib/osc-utilities.coffee @ 271:fb9c28a4676b prerelease

Added osc example project and node script for testing
author Liam Donovan <l.b.donovan@qmul.ac.uk>
date Tue, 17 May 2016 16:01:06 +0100
parents
children
rev   line source
l@271 1 # # osc-utilities.coffee
l@271 2 # ## Intro
l@271 3 # This file contains some lower-level utilities for OSC handling.
l@271 4 # My guess is client code won't need this. If you do need this, you must
l@271 5 # require coffee first, then write:
l@271 6 #
l@271 7 # require("coffee-script/register");
l@271 8 # osc-utils = require("osc/lib/osc-utilities");
l@271 9 #
l@271 10 # See the comments in osc.coffee for more information about the structure of
l@271 11 # the objects we're dealing with here.
l@271 12 #
l@271 13
l@271 14 # ## Dependencies
l@271 15 # require the minimal binary packing utilities
l@271 16 binpack = require "binpack"
l@271 17
l@271 18 # ## Exported Functions
l@271 19
l@271 20 # Utility for working with buffers. takes an array of buffers,
l@271 21 # output one buffer with all of the array concatenated
l@271 22 #
l@271 23 # This is really only exported for TDD, but maybe it'll be useful
l@271 24 # to someone else too.
l@271 25 exports.concat = (buffers) ->
l@271 26 if not IsArray buffers
l@271 27 throw new Error "concat must take an array of buffers"
l@271 28
l@271 29 for buffer in buffers
l@271 30 if not Buffer.isBuffer(buffer)
l@271 31 throw new Error "concat must take an array of buffers"
l@271 32
l@271 33 sumLength = 0
l@271 34 sumLength += buffer.length for buffer in buffers
l@271 35
l@271 36 destBuffer = new Buffer(sumLength)
l@271 37
l@271 38 copyTo = 0
l@271 39 for buffer in buffers
l@271 40 buffer.copy destBuffer, copyTo
l@271 41 copyTo += buffer.length
l@271 42
l@271 43 destBuffer
l@271 44
l@271 45 #
l@271 46 # Convert a javascript string into a node.js Buffer containing an OSC-String.
l@271 47 #
l@271 48 # str must not contain any \u0000 characters.
l@271 49 #
l@271 50 # `strict` is an optional boolean paramter that fails if the string is invalid
l@271 51 # (i.e. contains a \u0000 character)
l@271 52 exports.toOscString = (str, strict) ->
l@271 53 if not (typeof str == "string")
l@271 54 throw new Error "can't pack a non-string into an osc-string"
l@271 55
l@271 56 # strip off any \u0000 characters.
l@271 57 nullIndex = str.indexOf("\u0000")
l@271 58
l@271 59 # if we're being strict, we can't allow strings with null characters
l@271 60 if (nullIndex != -1 and strict)
l@271 61 throw StrictError "Can't pack an osc-string that contains NULL characters"
l@271 62
l@271 63 str = str[0...nullIndex] if nullIndex != -1
l@271 64
l@271 65 # osc-strings must have length divisible by 4 and end with at least one zero.
l@271 66 for i in [0...(padding str)]
l@271 67 str += "\u0000"
l@271 68
l@271 69 # create a new buffer from the string.
l@271 70 new Buffer(str)
l@271 71
l@271 72 #
l@271 73 # Try to split a buffer into a leading osc-string and the rest of the buffer,
l@271 74 # with the following layout:
l@271 75 # { string : "blah" rest : <Buffer>}.
l@271 76 #
l@271 77 # `strict`, as above, is an optional boolean parameter that defaults to false -
l@271 78 # if it is true, then an invalid buffer will always return null.
l@271 79 #
l@271 80 exports.splitOscString = (buffer, strict) ->
l@271 81 if not Buffer.isBuffer buffer
l@271 82 throw StrictError "Can't split something that isn't a buffer"
l@271 83
l@271 84 # extract the string
l@271 85 rawStr = buffer.toString "utf8"
l@271 86 nullIndex = rawStr.indexOf "\u0000"
l@271 87
l@271 88 # the rest of the code doesn't apply if there's no null character.
l@271 89 if nullIndex == -1
l@271 90 throw new Error "All osc-strings must contain a null character" if strict
l@271 91 return {string:rawStr, rest:(new Buffer 0)}
l@271 92
l@271 93 # extract the string.
l@271 94 str = rawStr[0...nullIndex]
l@271 95
l@271 96 # find the length of the string's buffer
l@271 97 splitPoint = Buffer.byteLength(str) + padding(str)
l@271 98
l@271 99 # in strict mode, don't succeed if there's not enough padding.
l@271 100 if strict and splitPoint > buffer.length
l@271 101 throw StrictError "Not enough padding for osc-string"
l@271 102
l@271 103 # if we're in strict mode, check that all the padding is null
l@271 104 if strict
l@271 105 for i in [Buffer.byteLength(str)...splitPoint]
l@271 106 if buffer[i] != 0
l@271 107 throw StrictError "Not enough or incorrect padding for osc-string"
l@271 108
l@271 109 # return a split
l@271 110 rest = buffer[splitPoint...(buffer.length)]
l@271 111
l@271 112 {string: str, rest: rest}
l@271 113
l@271 114 # This has similar semantics to splitOscString but works with integers instead.
l@271 115 # bytes is the number of bytes in the integer, defaults to 4.
l@271 116 exports.splitInteger = (buffer, type) ->
l@271 117 type = "Int32" if not type?
l@271 118 bytes = (binpack["pack" + type] 0).length
l@271 119
l@271 120 if buffer.length < bytes
l@271 121 throw new Error "buffer is not big enough for integer type"
l@271 122
l@271 123 num = 0
l@271 124
l@271 125 # integers are stored in big endian format.
l@271 126 value = binpack["unpack" + type] buffer[0...bytes], "big"
l@271 127
l@271 128 rest = buffer[bytes...(buffer.length)]
l@271 129
l@271 130 return {integer : value, rest : rest}
l@271 131
l@271 132 # Split off an OSC timetag from buffer
l@271 133 # returning {timetag: [seconds, fractionalSeconds], rest: restOfBuffer}
l@271 134 exports.splitTimetag = (buffer) ->
l@271 135 type = "Int32"
l@271 136 bytes = (binpack["pack" + type] 0).length
l@271 137
l@271 138 if buffer.length < (bytes * 2)
l@271 139 throw new Error "buffer is not big enough to contain a timetag"
l@271 140
l@271 141 # integers are stored in big endian format.
l@271 142 a = 0
l@271 143 b = bytes
l@271 144 seconds = binpack["unpack" + type] buffer[a...b], "big"
l@271 145 c = bytes
l@271 146 d = bytes + bytes
l@271 147 fractional = binpack["unpack" + type] buffer[c...d], "big"
l@271 148 rest = buffer[d...(buffer.length)]
l@271 149
l@271 150 return {timetag: [seconds, fractional], rest: rest}
l@271 151
l@271 152 UNIX_EPOCH = 2208988800
l@271 153 TWO_POW_32 = 4294967296
l@271 154
l@271 155 # Convert a JavaScript Date to a NTP timetag array.
l@271 156 # Time zone of the Date object is respected, as the NTP
l@271 157 # timetag uses UTC.
l@271 158 exports.dateToTimetag = (date) ->
l@271 159 return exports.timestampToTimetag(date.getTime() / 1000)
l@271 160
l@271 161 # Convert a unix timestamp (seconds since jan 1 1970 UTC)
l@271 162 # to NTP timestamp array
l@271 163 exports.timestampToTimetag = (secs) ->
l@271 164 wholeSecs = Math.floor(secs)
l@271 165 fracSeconds = secs - wholeSecs
l@271 166 return makeTimetag(wholeSecs, fracSeconds)
l@271 167
l@271 168 # Convert a timetag to unix timestamp (seconds since unix epoch)
l@271 169 exports.timetagToTimestamp = (timetag) ->
l@271 170 seconds = timetag[0] + exports.ntpToFractionalSeconds(timetag[1])
l@271 171 return seconds - UNIX_EPOCH
l@271 172
l@271 173 makeTimetag = (unixseconds, fracSeconds) ->
l@271 174 # NTP epoch is 1900, JavaScript Date is unix 1970
l@271 175 ntpSecs = unixseconds + UNIX_EPOCH
l@271 176 ntpFracs = Math.round(TWO_POW_32 * fracSeconds)
l@271 177 return [ntpSecs, ntpFracs]
l@271 178
l@271 179 # Convert NTP timestamp array to a JavaScript Date
l@271 180 # in your systems local time zone.
l@271 181 exports.timetagToDate = (timetag) ->
l@271 182 [seconds, fractional] = timetag
l@271 183 seconds = seconds - UNIX_EPOCH
l@271 184 fracs = exports.ntpToFractionalSeconds(fractional)
l@271 185 date = new Date()
l@271 186 # Sets date to UTC/GMT
l@271 187 date.setTime((seconds * 1000) + (fracs * 1000))
l@271 188 # Create a local timezone date
l@271 189 dd = new Date()
l@271 190 dd.setUTCFullYear(date.getUTCFullYear())
l@271 191 dd.setUTCMonth(date.getUTCMonth())
l@271 192 dd.setUTCDate(date.getUTCDate())
l@271 193 dd.setUTCHours(date.getUTCHours())
l@271 194 dd.setUTCMinutes(date.getUTCMinutes())
l@271 195 dd.setUTCSeconds(date.getUTCSeconds())
l@271 196 dd.setUTCMilliseconds(fracs * 1000)
l@271 197 return dd
l@271 198
l@271 199 # Make NTP timestamp array for relative future: now + seconds
l@271 200 # Accuracy of 'now' limited to milliseconds but 'seconds' may be a full 32 bit float
l@271 201 exports.deltaTimetag = (seconds, now) ->
l@271 202 n = (now ? new Date()) / 1000
l@271 203 return exports.timestampToTimetag(n + seconds)
l@271 204
l@271 205 # Convert 32 bit int for NTP fractional seconds
l@271 206 # to a 32 bit float
l@271 207 exports.ntpToFractionalSeconds = (fracSeconds) ->
l@271 208 return parseFloat(fracSeconds) / TWO_POW_32
l@271 209
l@271 210 # Encodes a timetag of type null|Number|Array|Date
l@271 211 # as a Buffer for adding to an OSC bundle.
l@271 212 exports.toTimetagBuffer = (timetag) ->
l@271 213 if typeof timetag is "number"
l@271 214 timetag = exports.timestampToTimetag(timetag)
l@271 215 else if typeof timetag is "object" and ("getTime" of timetag)
l@271 216 # quacks like a Date
l@271 217 timetag = exports.dateToTimetag(timetag)
l@271 218 else if timetag.length != 2
l@271 219 throw new Error("Invalid timetag" + timetag)
l@271 220 type = "Int32"
l@271 221 high = binpack["pack" + type] timetag[0], "big"
l@271 222 low = binpack["pack" + type] timetag[1], "big"
l@271 223 return exports.concat([high, low])
l@271 224
l@271 225 exports.toIntegerBuffer = (number, type) ->
l@271 226 type = "Int32" if not type?
l@271 227 if typeof number isnt "number"
l@271 228 throw new Error "cannot pack a non-number into an integer buffer"
l@271 229 binpack["pack" + type] number, "big"
l@271 230
l@271 231 # This mapping contains three fields for each type:
l@271 232 # - representation : the javascript string representation of this type.
l@271 233 # - split : a function to split a buffer into a decoded value and
l@271 234 # the rest of the buffer.
l@271 235 # - toArg : a function that takes the representation of the type and
l@271 236 # outputs a buffer.
l@271 237 oscTypeCodes =
l@271 238 s : {
l@271 239 representation : "string"
l@271 240 split : (buffer, strict) ->
l@271 241 # just pass it through to splitOscString
l@271 242 split = exports.splitOscString buffer, strict
l@271 243 {value : split.string, rest : split.rest}
l@271 244 toArg : (value, strict) ->
l@271 245 throw new Error "expected string" if typeof value isnt "string"
l@271 246 exports.toOscString value, strict
l@271 247 }
l@271 248 i : {
l@271 249 representation : "integer"
l@271 250 split : (buffer, strict) ->
l@271 251 split = exports.splitInteger buffer
l@271 252 {value : split.integer, rest : split.rest}
l@271 253 toArg : (value, strict) ->
l@271 254 throw new Error "expected number" if typeof value isnt "number"
l@271 255 exports.toIntegerBuffer value
l@271 256 }
l@271 257 t : {
l@271 258 representation : "timetag"
l@271 259 split : (buffer, strict) ->
l@271 260 split = exports.splitTimetag buffer
l@271 261 {value: split.timetag, rest: split.rest}
l@271 262 toArg : (value, strict) ->
l@271 263 exports.toTimetagBuffer value
l@271 264 }
l@271 265 f : {
l@271 266 representation : "float"
l@271 267 split : (buffer, strict) ->
l@271 268 value : (binpack.unpackFloat32 buffer[0...4], "big")
l@271 269 rest : buffer[4...(buffer.length)]
l@271 270 toArg : (value, strict) ->
l@271 271 throw new Error "expected number" if typeof value isnt "number"
l@271 272 binpack.packFloat32 value, "big"
l@271 273 }
l@271 274 d : {
l@271 275 representation : "double"
l@271 276 split : (buffer, strict) ->
l@271 277 value : (binpack.unpackFloat64 buffer[0...8], "big")
l@271 278 rest : buffer[8...(buffer.length)]
l@271 279 toArg : (value, strict) ->
l@271 280 throw new Error "expected number" if typeof value isnt "number"
l@271 281 binpack.packFloat64 value, "big"
l@271 282 }
l@271 283 b : {
l@271 284 representation : "blob"
l@271 285 split : (buffer, strict) ->
l@271 286 # not much to do here, first grab an 4 byte int from the buffer
l@271 287 {integer : length, rest : buffer} = exports.splitInteger buffer
l@271 288 {value : buffer[0...length], rest : buffer[length...(buffer.length)]}
l@271 289 toArg : (value, strict) ->
l@271 290 throw new Error "expected node.js Buffer" if not Buffer.isBuffer value
l@271 291 size = exports.toIntegerBuffer value.length
l@271 292 exports.concat [size, value]
l@271 293 }
l@271 294 T : {
l@271 295 representation : "true"
l@271 296 split : (buffer, strict) ->
l@271 297 rest : buffer
l@271 298 value : true
l@271 299 toArg : (value, strict) ->
l@271 300 throw new Error "true must be true" if not value and strict
l@271 301 new Buffer 0
l@271 302 }
l@271 303 F : {
l@271 304 representation : "false"
l@271 305 split : (buffer, strict) ->
l@271 306 rest : buffer
l@271 307 value : false
l@271 308 toArg : (value, strict) ->
l@271 309 throw new Error "false must be false" if value and strict
l@271 310 new Buffer 0
l@271 311 }
l@271 312 N : {
l@271 313 representation : "null"
l@271 314 split : (buffer, strict) ->
l@271 315 rest : buffer
l@271 316 value : null
l@271 317 toArg : (value, strict) ->
l@271 318 throw new Error "null must be false" if value and strict
l@271 319 new Buffer 0
l@271 320 }
l@271 321 I : {
l@271 322 representation : "bang"
l@271 323 split : (buffer, strict) ->
l@271 324 rest : buffer
l@271 325 value : "bang"
l@271 326 toArg : (value, strict) ->
l@271 327 new Buffer 0
l@271 328 }
l@271 329
l@271 330 # simple function that converts a type code into it's javascript
l@271 331 # string representation.
l@271 332 exports.oscTypeCodeToTypeString = (code) ->
l@271 333 oscTypeCodes[code]?.representation
l@271 334
l@271 335 # simple function that converts a javascript string representation
l@271 336 # into its OSC type code.
l@271 337 exports.typeStringToOscTypeCode = (rep) ->
l@271 338 for own code, {representation : str} of oscTypeCodes
l@271 339 return code if str is rep
l@271 340 return null
l@271 341
l@271 342 exports.argToTypeCode = (arg, strict) ->
l@271 343 # if there's an explicit type annotation, back-translate that.
l@271 344 if arg?.type? and
l@271 345 (typeof arg.type is 'string') and
l@271 346 (code = exports.typeStringToOscTypeCode arg.type)?
l@271 347 return code
l@271 348
l@271 349 value = if arg?.value? then arg.value else arg
l@271 350
l@271 351 # now, we try to guess the type.
l@271 352 throw new Error 'Argument has no value' if strict and not value?
l@271 353
l@271 354 # if it's a string, use 's'
l@271 355 if typeof value is 'string'
l@271 356 return 's'
l@271 357
l@271 358 # if it's a number, use 'f' by default.
l@271 359 if typeof value is 'number'
l@271 360 return 'f'
l@271 361
l@271 362 # if it's a buffer, use 'b'
l@271 363 if Buffer.isBuffer(value)
l@271 364 return 'b'
l@271 365
l@271 366 #### These are 1.1 specific types.
l@271 367
l@271 368 # if it's a boolean, use 'T' or 'F'
l@271 369 if typeof value is 'boolean'
l@271 370 if value then return 'T' else return 'F'
l@271 371
l@271 372 # if it's null, use 'N'
l@271 373 if value is null
l@271 374 return 'N'
l@271 375
l@271 376 throw new Error "I don't know what type this is supposed to be."
l@271 377
l@271 378 # Splits out an argument from buffer. Same thing as splitOscString but
l@271 379 # works for all argument types.
l@271 380 exports.splitOscArgument = (buffer, type, strict) ->
l@271 381 osctype = exports.typeStringToOscTypeCode type
l@271 382 if osctype?
l@271 383 oscTypeCodes[osctype].split buffer, strict
l@271 384 else
l@271 385 throw new Error "I don't understand how I'm supposed to unpack #{type}"
l@271 386
l@271 387 # Create a buffer with the given javascript type
l@271 388 exports.toOscArgument = (value, type, strict) ->
l@271 389 osctype = exports.typeStringToOscTypeCode type
l@271 390 if osctype?
l@271 391 oscTypeCodes[osctype].toArg value, strict
l@271 392 else
l@271 393 throw new Error "I don't know how to pack #{type}"
l@271 394
l@271 395 #
l@271 396 # translates an OSC message into a javascript representation.
l@271 397 #
l@271 398 exports.fromOscMessage = (buffer, strict) ->
l@271 399 # break off the address
l@271 400 { string : address, rest : buffer} = exports.splitOscString buffer, strict
l@271 401
l@271 402 # technically, addresses have to start with '/'.
l@271 403 if strict and address[0] isnt '/'
l@271 404 throw StrictError 'addresses must start with /'
l@271 405
l@271 406 # if there's no type string, this is technically illegal, but
l@271 407 # the specification says we should accept this until all
l@271 408 # implementations that send message without a type string are fixed.
l@271 409 # this will never happen, so we should accept this, even in
l@271 410 # strict mode.
l@271 411 return {address : address, args : []} if not buffer.length
l@271 412
l@271 413 # if there's more data but no type string, we can't parse the arguments.
l@271 414 {string : types, rest : buffer} = exports.splitOscString buffer, strict
l@271 415
l@271 416 # if the first letter isn't a ',' this isn't a valid type so we can't
l@271 417 # parse the arguments.
l@271 418 if types[0] isnt ','
l@271 419 throw StrictError 'Argument lists must begin with ,' if strict
l@271 420 return {address : address, args : []}
l@271 421
l@271 422 # we don't need the comma anymore
l@271 423 types = types[1..(types.length)]
l@271 424
l@271 425 args = []
l@271 426
l@271 427 # we use this to build up array arguments.
l@271 428 # arrayStack[-1] is always the currently contructing
l@271 429 # array.
l@271 430 arrayStack = [args]
l@271 431
l@271 432 # grab each argument.
l@271 433 for type in types
l@271 434 # special case: we're beginning construction of an array.
l@271 435 if type is '['
l@271 436 arrayStack.push([])
l@271 437 continue
l@271 438
l@271 439 # special case: we've just finished constructing an array.
l@271 440 if type is ']'
l@271 441 if arrayStack.length <= 1
l@271 442 throw new StrictError "Mismatched ']' character." if strict
l@271 443 else
l@271 444 built = arrayStack.pop()
l@271 445 arrayStack[arrayStack.length-1].push(
l@271 446 type: 'array'
l@271 447 value: built
l@271 448 )
l@271 449 continue
l@271 450
l@271 451 # by the standard, we have to ignore the whole message
l@271 452 # if we don't understand an argument
l@271 453 typeString = exports.oscTypeCodeToTypeString type
l@271 454 if not typeString?
l@271 455 throw new Error "I don't understand the argument code #{type}"
l@271 456
l@271 457 arg = exports.splitOscArgument buffer, typeString, strict
l@271 458
l@271 459 # consume the argument from the buffer
l@271 460 buffer = arg.rest if arg?
l@271 461
l@271 462 # add it to the list.
l@271 463 arrayStack[arrayStack.length-1].push(
l@271 464 type : typeString
l@271 465 value : arg?.value
l@271 466 )
l@271 467
l@271 468 if arrayStack.length isnt 1 and strict
l@271 469 throw new StrictError "Mismatched '[' character"
l@271 470 {address : address, args : args, oscType : "message"}
l@271 471
l@271 472 #
l@271 473 # Try to parse an OSC bundle into a javascript object.
l@271 474 #
l@271 475 exports.fromOscBundle = (buffer, strict) ->
l@271 476 # break off the bundletag
l@271 477 { string : bundleTag, rest : buffer} = exports.splitOscString buffer, strict
l@271 478
l@271 479 # bundles have to start with "#bundle".
l@271 480 if bundleTag isnt "\#bundle"
l@271 481 throw new Error "osc-bundles must begin with \#bundle"
l@271 482
l@271 483 # grab the 8 byte timetag
l@271 484 {timetag: timetag, rest: buffer} = exports.splitTimetag buffer
l@271 485
l@271 486 # convert each element.
l@271 487 convertedElems = mapBundleList buffer, (buffer) ->
l@271 488 exports.fromOscPacket buffer, strict
l@271 489
l@271 490 return {timetag : timetag, elements : convertedElems, oscType : "bundle"}
l@271 491
l@271 492 #
l@271 493 # convert the buffer into a bundle or a message, depending on the first string
l@271 494 #
l@271 495 exports.fromOscPacket = (buffer, strict) ->
l@271 496 if isOscBundleBuffer buffer, strict
l@271 497 exports.fromOscBundle buffer, strict
l@271 498 else
l@271 499 exports.fromOscMessage buffer, strict
l@271 500
l@271 501 # helper - is it an argument that represents an array?
l@271 502 getArrayArg = (arg) ->
l@271 503 if IsArray arg
l@271 504 arg
l@271 505 else if (arg?.type is "array") and (IsArray arg?.value)
l@271 506 arg.value
l@271 507 else if arg? and (not arg.type?) and (IsArray arg.value)
l@271 508 arg.value
l@271 509 else
l@271 510 null
l@271 511
l@271 512 # helper - converts an argument list into a pair of a type string and a
l@271 513 # data buffer
l@271 514 # argList must be an array!!!
l@271 515 toOscTypeAndArgs = (argList, strict) ->
l@271 516 osctype = ""
l@271 517 oscargs = []
l@271 518 for arg in argList
l@271 519 if (getArrayArg arg)?
l@271 520 [thisType, thisArgs] = toOscTypeAndArgs (getArrayArg arg), strict
l@271 521 osctype += "[" + thisType + "]"
l@271 522 oscargs = oscargs.concat thisArgs
l@271 523 continue
l@271 524 typeCode = exports.argToTypeCode arg, strict
l@271 525 if typeCode?
l@271 526 value = arg?.value
l@271 527 if value is undefined
l@271 528 value = arg
l@271 529 buff = exports.toOscArgument value,
l@271 530 (exports.oscTypeCodeToTypeString typeCode), strict
l@271 531 if buff?
l@271 532 oscargs.push buff
l@271 533 osctype += typeCode
l@271 534 [osctype, oscargs]
l@271 535
l@271 536 #
l@271 537 # convert a javascript format message into an osc buffer
l@271 538 #
l@271 539 exports.toOscMessage = (message, strict) ->
l@271 540 # the message must have addresses and arguments.
l@271 541 address = if message?.address? then message.address else message
l@271 542 if typeof address isnt "string"
l@271 543 throw new Error "message must contain an address"
l@271 544
l@271 545 args = message?.args
l@271 546
l@271 547 if args is undefined
l@271 548 args = []
l@271 549
l@271 550 # pack single args
l@271 551 if not IsArray args
l@271 552 old_arg = args
l@271 553 args = []
l@271 554 args[0] = old_arg
l@271 555
l@271 556 oscaddr = exports.toOscString address, strict
l@271 557 [osctype, oscargs] = toOscTypeAndArgs args, strict
l@271 558 osctype = "," + osctype
l@271 559
l@271 560 # bundle everything together.
l@271 561 allArgs = exports.concat oscargs
l@271 562
l@271 563 # convert the type tag into an oscString.
l@271 564 osctype = exports.toOscString osctype
l@271 565
l@271 566 exports.concat [oscaddr, osctype, allArgs]
l@271 567
l@271 568 #
l@271 569 # convert a javascript format bundle into an osc buffer
l@271 570 #
l@271 571 exports.toOscBundle = (bundle, strict) ->
l@271 572 # the bundle must have timetag and elements.
l@271 573 if strict and not bundle?.timetag?
l@271 574 throw StrictError "bundles must have timetags."
l@271 575 timetag = bundle?.timetag ? new Date()
l@271 576 elements = bundle?.elements ? []
l@271 577 if not IsArray elements
l@271 578 elemstr = elements
l@271 579 elements = []
l@271 580 elements.push elemstr
l@271 581
l@271 582 oscBundleTag = exports.toOscString "\#bundle"
l@271 583 oscTimeTag = exports.toTimetagBuffer timetag
l@271 584
l@271 585 oscElems = []
l@271 586 for elem in elements
l@271 587 try
l@271 588 # try to convert this sub-element into a buffer
l@271 589 buff = exports.toOscPacket elem, strict
l@271 590
l@271 591 # okay, pack in the size.
l@271 592 size = exports.toIntegerBuffer buff.length
l@271 593 oscElems.push exports.concat [size, buff]
l@271 594 catch e
l@271 595 null
l@271 596
l@271 597 allElems = exports.concat oscElems
l@271 598 exports.concat [oscBundleTag, oscTimeTag, allElems]
l@271 599
l@271 600 # convert a javascript format bundle or message into a buffer
l@271 601 exports.toOscPacket = (bundleOrMessage, strict) ->
l@271 602 # first, determine whether or not this is a bundle.
l@271 603 if bundleOrMessage?.oscType?
l@271 604 if bundleOrMessage.oscType is "bundle"
l@271 605 return exports.toOscBundle bundleOrMessage, strict
l@271 606 return exports.toOscMessage bundleOrMessage, strict
l@271 607
l@271 608 # bundles have "timetags" and "elements"
l@271 609 if bundleOrMessage?.timetag? or bundleOrMessage?.elements?
l@271 610 return exports.toOscBundle bundleOrMessage, strict
l@271 611
l@271 612 exports.toOscMessage bundleOrMessage, strict
l@271 613
l@271 614 #
l@271 615 # Helper function for transforming all messages in a bundle with a given message
l@271 616 # transform.
l@271 617 #
l@271 618 exports.applyMessageTranformerToBundle = (transform) -> (buffer) ->
l@271 619
l@271 620 # parse out the bundle-id and the tag, we don't want to change these
l@271 621 { string, rest : buffer} = exports.splitOscString buffer
l@271 622
l@271 623 # bundles have to start with "#bundle".
l@271 624 if string isnt "\#bundle"
l@271 625 throw new Error "osc-bundles must begin with \#bundle"
l@271 626
l@271 627 bundleTagBuffer = exports.toOscString string
l@271 628
l@271 629 # we know that the timetag is 8 bytes, we don't want to mess with it,
l@271 630 # so grab it as a buffer. There is some subtle loss of precision with
l@271 631 # the round trip from int64 to float64.
l@271 632 timetagBuffer = buffer[0...8]
l@271 633 buffer = buffer[8...buffer.length]
l@271 634
l@271 635 # convert each element.
l@271 636 elems = mapBundleList buffer, (buffer) ->
l@271 637 exports.applyTransform(
l@271 638 buffer,
l@271 639 transform,
l@271 640 exports.applyMessageTranformerToBundle transform
l@271 641 )
l@271 642
l@271 643 totalLength = bundleTagBuffer.length + timetagBuffer.length
l@271 644 totalLength += 4 + elem.length for elem in elems
l@271 645
l@271 646 # okay, now we have to reconcatenate everything.
l@271 647 outBuffer = new Buffer totalLength
l@271 648 bundleTagBuffer.copy outBuffer, 0
l@271 649 timetagBuffer.copy outBuffer, bundleTagBuffer.length
l@271 650 copyIndex = bundleTagBuffer.length + timetagBuffer.length
l@271 651 for elem in elems
l@271 652 lengthBuff = exports.toIntegerBuffer elem.length
l@271 653 lengthBuff.copy outBuffer, copyIndex
l@271 654 copyIndex += 4
l@271 655 elem.copy outBuffer, copyIndex
l@271 656 copyIndex += elem.length
l@271 657 outBuffer
l@271 658
l@271 659 #
l@271 660 # Applies a transformation function (that is, a function from buffers
l@271 661 # to buffers) to each element of given osc-bundle or message.
l@271 662 #
l@271 663 # `buffer` is the buffer to transform, which must be a buffer of a full packet.
l@271 664 # `messageTransform` is function from message buffers to message buffers
l@271 665 # `bundleTransform` is an optional parameter for functions from bundle buffers
l@271 666 # to bundle buffers.
l@271 667 # if `bundleTransform` is not set, it defaults to just applying the
l@271 668 # `messageTransform` to each message in the bundle.
l@271 669 #
l@271 670 exports.applyTransform = (buffer, mTransform, bundleTransform) ->
l@271 671 if not bundleTransform?
l@271 672 bundleTransform = exports.applyMessageTranformerToBundle mTransform
l@271 673
l@271 674 if isOscBundleBuffer buffer
l@271 675 bundleTransform buffer
l@271 676 else
l@271 677 mTransform buffer
l@271 678
l@271 679 # Converts a javascript function from string to string to a function
l@271 680 # from message buffer to message buffer, applying the function to the
l@271 681 # parsed strings.
l@271 682 #
l@271 683 # We pre-curry this because we expect to use this with `applyMessageTransform`
l@271 684 # above
l@271 685 #
l@271 686 exports.addressTransform = (transform) -> (buffer) ->
l@271 687 # parse out the address
l@271 688 {string, rest} = exports.splitOscString buffer
l@271 689
l@271 690 # apply the function
l@271 691 string = transform string
l@271 692
l@271 693 # re-concatenate
l@271 694 exports.concat [
l@271 695 exports.toOscString string
l@271 696 rest
l@271 697 ]
l@271 698
l@271 699 #
l@271 700 # Take a function that transform a javascript _OSC Message_ and
l@271 701 # convert it to a function that transforms osc-buffers.
l@271 702 #
l@271 703 exports.messageTransform = (transform) -> (buffer) ->
l@271 704 message = exports.fromOscMessage buffer
l@271 705 exports.toOscMessage transform message
l@271 706
l@271 707 ## Private utilities
l@271 708
l@271 709 #
l@271 710 # is it an array?
l@271 711 #
l@271 712 IsArray = Array.isArray
l@271 713
l@271 714 #
l@271 715 # An error that only throws when we're in strict mode.
l@271 716 #
l@271 717 StrictError = (str) ->
l@271 718 new Error "Strict Error: " + str
l@271 719
l@271 720 # this private utility finds the amount of padding for a given string.
l@271 721 padding = (str) ->
l@271 722 bufflength = Buffer.byteLength(str)
l@271 723 4 - (bufflength % 4)
l@271 724
l@271 725 #
l@271 726 # Internal function to check if this is a message or bundle.
l@271 727 #
l@271 728 isOscBundleBuffer = (buffer, strict) ->
l@271 729 # both formats begin with strings, so we should just grab the front but not
l@271 730 # consume it.
l@271 731 {string} = exports.splitOscString buffer, strict
l@271 732
l@271 733 return string is "\#bundle"
l@271 734
l@271 735 #
l@271 736 # Does something for each element in an array of osc-message-or-bundles,
l@271 737 # each prefixed by a length (such as appears in osc-messages), then
l@271 738 # return the result as an array.
l@271 739 #
l@271 740 # This is not exported because it doesn't validate the format and it's
l@271 741 # not really a generally useful function.
l@271 742 #
l@271 743 # If a function throws on an element, we discard that element in the map
l@271 744 # but we don't give up completely.
l@271 745 #
l@271 746 mapBundleList = (buffer, func) ->
l@271 747 elems = while buffer.length
l@271 748 # the length of the element is stored in an integer
l@271 749 {integer : size, rest : buffer} = exports.splitInteger buffer
l@271 750
l@271 751 # if the size is bigger than the packet, something's messed up, so give up.
l@271 752 if size > buffer.length
l@271 753 throw new Error(
l@271 754 "Invalid bundle list: size of element is bigger than buffer")
l@271 755
l@271 756 thisElemBuffer = buffer[0...size]
l@271 757
l@271 758 # move the buffer to after the element we're just parsing.
l@271 759 buffer = buffer[size...buffer.length]
l@271 760
l@271 761 # record this element
l@271 762 try
l@271 763 func thisElemBuffer
l@271 764 catch e
l@271 765 null
l@271 766
l@271 767 # remove all null from elements
l@271 768 nonNullElems = []
l@271 769 for elem in elems
l@271 770 (nonNullElems.push elem) if elem?
l@271 771
l@271 772 nonNullElems