;; -*- mode: lisp -*- ;; ;; an implementation of the OSC (Open Sound Control) protocol ;; ;; copyright (C) 2004 FoAM vzw. ;; ;; You are granted the rights to distribute and use this software ;; under the terms of the Lisp Lesser GNU Public License, known ;; as the LLGPL. The LLGPL consists of a preamble and the LGPL. ;; Where these conflict, the preamble takes precedence. The LLGPL ;; is available online at http://opensource.franz.com/preamble.html ;; and is distributed with this code (see: LICENCE and LGPL files) ;; ;; authors ;; ;; nik gaffney ;; ;; requirements ;; ;; dependent on sbcl or cmucl for float encoding, other suggestions welcome. ;; ;; commentary ;; ;; this is a partial implementation of the OSC protocol which is used ;; for communication mostly amongst music programs and their attatched ;; musicians. eg. sc3, max/pd, reaktor/traktorska etc+. more details ;; of the protocol can be found at the open sound control pages -=> ;; http://www.cnmat.berkeley.edu/OpenSoundControl/ ;; ;; - doesnt send nested bundles or timetags later than 'now' ;; - will most likely crash if the input is malformed ;; - int32 en/de-coding based on code (c) Walter C. Pelissero ;; ;; see the README file for more details... ;; ;; known BUGS ;; - only unknown for now.. . (defpackage :osc (:use :cl) (:documentation "OSC aka the 'open sound control' protocol") (:export :encode-message :encode-bundle :decode-message :decode-bundle)) (in-package :osc) ;(declaim (optimize (speed 3) (safety 1))) ;;;;;; ; ;; ; ; ; ; ; ; ; ;; ;; eNcoding OSC messages ;; ;;; ;; ;; ; ; ;; ; ; ; ; (defun encode-bundle (data) "will encode an osc message, or list of messages as a bundle with an optional timetag. doesnt handle nested bundles" (cat '(35 98 117 110 100 108 101 0) ; #bundle (encode-timetag :now) (if (listp (car data)) (apply #'cat (mapcar #'encode-bundle-elt data)) (encode-bundle-elt data)))) (defun encode-bundle-elt (data) (let ((message (apply #'encode-message data))) (cat (encode-int32 (length message)) message))) (defun encode-message (address &rest data) "encodes an osc message with the given address and data." (concatenate '(vector (unsigned-byte 8)) (encode-address address) (encode-typetags data) (encode-data data))) (defun encode-address (address) (cat (map 'vector #'char-code address) (string-padding address))) (defun encode-typetags (data) "creates a typetag string suitable for the given data. valid typetags according to the osc spec are ,i ,f ,s and ,b non-std extensions include ,{h|t|d|S|c|r|m|T|F|N|I|[|]} see the spec for more details. .. NOTE: currently handles the following tags i => #(105) => int32 f => #(102) => float s => #(115) => string b => #(98) => blob" (let ((lump (make-array 0 :adjustable t :fill-pointer t :element-type 'char))) (macrolet ((write-to-vector (char) `(vector-push-extend (char-code ,char) lump))) (write-to-vector #\,) (dolist (x data) (typecase x (integer (write-to-vector #\i)) (float (write-to-vector #\f)) (simple-string (write-to-vector #\s)) (simple-vector (write-to-vector #\b)) (t (error "can only encode ints, floats or strings")))) (cat lump (pad (padding-length (length lump))))))) (defun encode-data (data) "encodes data in a format suitable for an OSC message" (let ((lump (make-array 0 :adjustable t :fill-pointer t))) (macrolet ((enc (f) `(setf lump (cat lump (,f x))))) (dolist (x data) (typecase x (integer (enc encode-int32)) (float (enc encode-float32)) (simple-string (enc encode-string)) (simple-vector (enc encode-blob)) (t (error "wrong type. turn back")))) lump))) ;;;;;; ; ;; ; ; ; ; ; ; ; ;; ;; decoding OSC messages ;; ;;; ;; ;; ; ; ; ; ; ; (defun decode-bundle (data) "decodes an osc bundle into a list of decoded-messages, which has an osc-timetagas its first element" (let ((contents '())) (if (equalp 35 (elt data 0)) ; a bundle begins with '#' (let ((timetag (subseq data 8 16)) (i 16) (bundle-length (length data))) (loop while (< i bundle-length) do (let ((mark (+ i 4)) (size (decode-int32 (subseq data i (+ i 4))))) (if (eq size 0) (setf bundle-length 0) (push (decode-bundle (subseq data mark (+ mark size))) contents)) (incf i (+ 4 size)))) (push timetag contents)) (decode-message data)))) (defun decode-message (message) "reduces an osc message to an (address . data) pair. .." (declare (type (vector *) message)) (let ((x (position (char-code #\,) message))) (if (eq x NIL) (format t "message contains no data.. ") (cons (decode-address (subseq message 0 x)) (decode-taged-data (subseq message x)))))) (defun decode-address (address) (coerce (map 'vector #'code-char (delete 0 address)) 'string)) (defun decode-taged-data (data) "decodes data encoded with typetags... NOTE: currently handles the following tags i => #(105) => int32 f => #(102) => float s => #(115) => string b => #(98) => blob" (let ((div (position 0 data))) (let ((tags (subseq data 1 div)) (acc (subseq data (padded-length div))) (result '())) (map 'vector #'(lambda (x) (cond ((eq x (char-code #\i)) (push (decode-int32 (subseq acc 0 4)) result) (setf acc (subseq acc 4))) ((eq x (char-code #\f)) (push (decode-float32 (subseq acc 0 4)) result) (setf acc (subseq acc 4))) ((eq x (char-code #\s)) (let ((pointer (padded-length (position 0 acc)))) (push (decode-string (subseq acc 0 pointer)) result) (setf acc (subseq acc pointer)))) ((eq x (char-code #\b)) (let ((size (decode-int32 (subseq acc 0 4)))) (let ((end (padded-length (+ 4 size)))) (push (decode-blob (subseq acc 0 end)) result) (setf acc (subseq acc end))))) (t (error "unrecognised typetag")))) tags) (nreverse result)))) ;;; ; ;; ; ;; ;; timetags ;; ;; - not yet, but probably something using ;; (get-universal-time) > see also: CLHS 25.1.4 Time ;; or connecting to an ntp server.,. - ntpdate, ntpq ;; ;; - begin with bundles using 'now' as the timetag ;; - this should really handle 64bit fixed ints, ;; not signed 32bit ints ;; ;;;; ;; ; ; (defconstant +unix-epoch+ (encode-universal-time 0 0 0 1 1 1970 0)) (defun encode-timetag (ut &optional subseconds) "encodes an osc timetag from a universal-time and 32bit 'sub-second' part for an 'instantaneous' timetag use (encode-timetag :now) " (if (equalp ut :now) #(0 0 0 0 0 0 0 1) (cat (encode-int32 (+ ut +unix-epoch+)) (encode-int32 (subseconds))))) (defun decode-timetag (timetag) "decomposes a timetag into ut and a subsecond,. . ." (list (decode-int32 (subseq timetag 0 4)) (decode-int32 (subseq timetag 4 8)))) ;;;;; ; ; ;; ;; ; ; ;; ;; dataformat en- de- cetera. ;; ;;; ;; ; ; ; (defun encode-float32 (f) "encode an ieee754 float as a 4 byte vector. currently sbcl/cmucl specifc" #+sbcl (encode-int32 (sb-kernel:single-float-bits f)) #+cmucl (encode-int32 (kernel:single-float-bits f)) #-(or sbcl cmucl) (error "cant encode floats using this implementation")) (defun decode-float32 (s) "ieee754 float from a vector of 4 bytes in network byte order" #+sbcl (sb-kernel:make-single-float (decode-int32 s)) #+cmucl (kernel:make-single-float (decode-int32 s)) #-(or sbcl cmucl) (error "cant decode floats using this implementation")) (defun decode-int32 (s) "4 byte > 32 bit int > two's compliment (in network byte order)" (let ((i (+ (ash (elt s 0) 24) (ash (elt s 1) 16) (ash (elt s 2) 8) (elt s 3)))) (if (>= i #x7fffffff) (- 0 (- #x100000000 i)) i))) (defun encode-int32 (i) "convert integer into a sequence of 4 bytes in network byte order." (declare (type integer i n)) (let ((buf (make-sequence '(vector (unsigned-byte 8)) 4))) (macrolet ((set-byte (n) `(setf (elt buf ,n) (logand #xff (ash i ,(* 8 (- n 3))))))) (set-byte 0) (set-byte 1) (set-byte 2) (set-byte 3)) buf)) ;; osc-strings are unsigned bytes, padded to a 4 byte boundary (defun decode-string (data) "converts a binary vector to a string and removes trailing #\nul characters" (string-trim '(#\nul) (coerce (map 'vector #'code-char data) 'string))) (defun encode-string (string) "encodes a string as a vector of character-codes, padded to 4 byte boundary" (cat (map 'vector #'char-code string) (string-padding string))) ;; blobs are binary data, consisting of a length (int32) and bytes which are ;; osc-padded to a 4 byte boundary. (defun decode-blob (blob) "decode a blob as a vector of unsigned bytes." (let ((size (decode-int32 (subseq blob 0 4)))) (subseq blob 4 (+ 4 size)))) (defun encode-blob (blob) "encodes a blob from a given vector" (let ((bl (length blob))) (cat (encode-int32 bl) blob (pad (padding-length bl))))) ;; utility functions for osc-string/padding slonking (defun cat (&rest catatac) (apply #'concatenate '(vector *) catatac)) (defun padding-length (s) "returns the length of padding required for a given length of string" (declare (type fixnum s)) (- 4 (mod s 4))) (defun padded-length (s) "returns the length of an osc-string made from a given length of string" (declare (type fixnum s)) (+ s (- 4 (mod s 4)))) (defun string-padding (string) "returns the padding required for a given osc string" (declare (type simple-string string)) (pad (- 4 (mod (length string) 4)))) (defun pad (n) "make a sequence of the required number of #\Nul characters" (declare (type fixnum n)) (make-array n :initial-element 0 :fill-pointer n)) ;; end