OSC: Open Sound Control Byte String Conversion
osc-element | = | (osc-bundle timestamp (list osc-element ...)) | ||
| | (osc-message address (list osc-value ...)) |
procedure
(osc-element->bytes element) → bytes?
element : osc-element?
Here’s an example of using it:
(osc-element->bytes (osc-message #"/abc/def" (list 3 6 2.278 #"froggy" `(blob #"derple"))))
produces:
#"/abc/def\0\0\0\0,iifsb\0\0\0\0\0\3\0\0\0\6@\21\312\301froggy\0\0\0\0\0\6derple\0\0"
procedure
(bytes->osc-element bytes) → osc-element?
bytes : bytes?
Here’s an example of using it:
(bytes->osc-element #"/abc/def\0\0\0\0,iifsb\0\0\0\0\0\3\0\0\0\6@\21\312\301froggy\0\0\0\0\0\6derple\0\0")
produces
(osc-message #"/abc/def" (3 6 2.2780001163482666 #"froggy" (blob #"derple")))
Composing these two should be the identity for legal OSC elements up to number inexactness, as seen here (or legal byte strings, if composed the other way).
procedure
(osc-element? value) → boolean?
value : any/c
An OSC Element is either a bundle or a message.
struct
(struct osc-bundle (timestamp elements) #:prefab) timestamp : osc-date? elements : (listof osc-element?)
An OSC Message consists of an address and arguments:
struct
(struct osc-message (address args) #:prefab) address : byte-string? args : (listof osc-value?)
An OSC value is one of a number of different kinds of s-expressions. Let me know if you can see a better way to document this:
procedure
(osc-value? value) → boolean?
value : any/c
(define (osc-value? v) (or (int32? v) ; just the number (int64? v) ; (list 'h number) (osc-date? v) ; either 'now or a list of two uint32s (float32? v) ; just the [inexact] number (osc-double? v) ; (list 'd <inexact>) (no-nul-bytes? v) ; a byte-string (osc-symbol? v) ; (list 'S <byte-string>) (blob? v) ; (list 'blob <byte-string>) (osc-char? v) ; (list 'c byte) (osc-color? v) ; (list 'r <4bytes>) (osc-midi? v) ; (list 'm <4bytes>) (boolean? v) ; boolean? (null? v) (osc-inf? v) ; 'infinitum (osc-array? v) ; (list 'arr (listof osc-value?))))
1 OSC Date conversion
OSC represents dates using two 32-bit numbers, essentially equivalent to a fixed-point 64-bit number with 32 bits before and 32 bits after the decimal point.
Racket’s (current-inexact-milliseconds) represents time using a 64-bit floating point number of milliseconds. Since a 64-bit float spends some bits on exponent and sign, you might be concerned that this representation is insufficiently precise.
Don’t be.
More specifically, a 64-bit float has approximately 52 bits of mantissa. Even if we use a full 32 to represent the seconds part, we’re left with time increments of 2^{-20} seconds. At a sample rate of 44.1 KHz, this gives us precision of about 1/25 of a sample, which should be plenty.
In other words, you should feel just fine about representing time using racket’s inexact-milliseconds, and converting to osc-dates only when sending the messages.
procedure
(milliseconds->osc-date milliseconds) → osc-date?
milliseconds : inexact-real?
procedure
(osc-date->milliseconds osc-date) → inexact-real?
osc-date : osc-date?
procedure
(seconds->osc-date seconds frac) → osc-date?
seconds : exact-integer? frac : inexact-real?
procedure
(osc-date->seconds-and-frac osc-date)
→ (list/c exact-integer? inexact-real?) osc-date : osc-date?
2 Reporting Bugs
For Heaven’s sake, report lots of bugs!