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
|