Mercurial > hg > beaglert
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 |