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