diff --git a/README.org b/README.org index dfcd0e8..92579eb 100644 --- a/README.org +++ b/README.org @@ -22,9 +22,9 @@ This implementation supports the [[https://opensoundcontrol.stanford.edu/spec-1_ | *Type tag* | *type* | *description* | *v1.0* | *v1.1* | *cl-osc* | | i | int32 | 32-bit big-endian two’s complement integer | *R* | *R* | YES | | f | float32 | 32-bit big-endian IEEE 754 floating point number | *R* | *R* | YES | -| s | OSC-string | A sequence of non-null ASCII characters followed by a null, | | *R* | | +| s | OSC-string | A sequence of non-null ASCII characters followed by a null… | | *R* | | | | | followed by 0-3 additional null characters. Total bits is a multiple of 32. | *R* | N | YES | -| b | OSC-blob | An int32 size count, followed by that many 8-bit bytes of arbitrary binary data, | | *R* | | +| b | OSC-blob | An int32 size count, followed by that many 8-bit bytes of arbitrary binary data… | | *R* | | | | | followed by 0-3 additional zero bytes. Total bits is a multiple of 32. | *R* | N | YES | | T | True | No bytes are allocated in the argument data. | O | *R* | | | F | False | No bytes are allocated in the argument data. | O | *R* | | @@ -32,7 +32,7 @@ This implementation supports the [[https://opensoundcontrol.stanford.edu/spec-1_ | I | Impulse | (aka “bang”), used for event triggers. No bytes are allocated in the argument data. | O | *R* | | | t | OSC-timetag | an OSC timetag in NTP format, encoded in the data section | O | *R* | | | h | int64 | 64 bit big-endian two’s complement integer | O | O | YES | -| d | float64 | 64 bit (“double”) IEEE 754 floating point number | O | O | | +| d | float64 | 64 bit (“double”) IEEE 754 floating point number | O | O | YES | | S | OSC-string | Alternate type represented as an OSC-string (e.g to differentiate “symbols” from “strings”) | O | O | YES | | c | | an ascii character, sent as 32 bits | O | O | | | r | | 32 bit RGBA color | O | O | | @@ -41,49 +41,5 @@ This implementation supports the [[https://opensoundcontrol.stanford.edu/spec-1_ | ] | | Indicates the end of an array. | O | O | YES? | -- *R*equired, *O*ptional and *N*ot supported (or *N*ot required). +- Required, Optional and Not supported (or Not required). - data is encoded as =(vector (unsigned 8))= by =cl-osc= - -* Float encoding & decoding - -#+BEGIN_SRC lisp -(defun encode-float32 (f) - "Encode an ieee754 float as a 4 byte vector. currently sbcl/cmucl specific." - (encode-int32 (ieee-floats:encode-float32 f))) - ;; #+sbcl (encode-int32 (sb-kernel:single-float-bits f)) - ;; #+cmucl (encode-int32 (kernel:single-float-bits f)) - ;; #+openmcl (encode-int32 (CCL::SINGLE-FLOAT-BITS f)) - ;; #+allegro (encode-int32 (multiple-value-bind (x y) - ;; (excl:single-float-to-shorts f) - ;; (+ (ash x 16) y))) - ;; #-(or sbcl cmucl openmcl allegro ieee-floats) (error "Can't encode single-floats using this implementation.")) -#+END_SRC - -#+BEGIN_SRC lisp -(defun decode-float32 (v) - "Convert a vector of 4 bytes in network byte order into an ieee754 float." - (ieee-floats:decode-float32 (decode-int32 v))) - ;; #+sbcl (sb-kernel:make-single-float (decode-int32 v)) - ;; #+cmucl (kernel:make-single-float (decode-int32 v)) - ;; #+openmcl (CCL::HOST-SINGLE-FLOAT-FROM-UNSIGNED-BYTE-32 (decode-uint32 v)) - ;; #+allegro (excl:shorts-to-single-float (ldb (byte 16 16) (decode-int32 v)) - ;; (ldb (byte 16 0) (decode-int32 v))) - ;; #-(or sbcl cmucl openmcl allegro) (error "Can't decode single-floats using this implementation.")) -#+END_SRC - -#+BEGIN_SRC lisp -(defun encode-float64 (d) - "Encode an ieee754 float as a 8 byte vector. currently sbcl/cmucl specific." - #+sbcl (cat (encode-int32 (sb-kernel:double-float-high-bits d)) - (encode-int32 (sb-kernel:double-float-low-bits d))) - #-(or sbcl ieee-floats) (error "Can't encode double-floats using this implementation.")) -#+END_SRC - -#+BEGIN_SRC lisp -(defun decode-float64 (v) - "Convert a vector of 8 bytes in network byte order into an ieee754 float." - #+sbcl (sb-kernel:make-double-float - (decode-uint32 (subseq v 0 4)) - (decode-uint32 (subseq v 4 8))) - #-(or sbcl ieee-floats) (error "Can't decode single-floats using this implementation.")) -#+END_SRC diff --git a/osc-tests.lisp b/osc-tests.lisp index cff6a2b..5b5b87e 100644 --- a/osc-tests.lisp +++ b/osc-tests.lisp @@ -66,6 +66,9 @@ (is (equalp (osc::decode-float32 #(254 254 254 254)) -1.6947395e38))) +;; (osc::decode-float32 #(127 255 255 255)) +;; # + (test osc-string "OSC string encoding tests." (is (equalp @@ -290,6 +293,28 @@ ;; v1.1. tests (in-suite protocol-v1.1) +(test v1.1-required-data-types + "OSC data encoding test. All required types for v1.1" + (is (equalp + #(44 105 104 115 102 100 98 0) + (osc::encode-typetags '(3 + 4294967297 + "test" + 2.1e2 + 2.1d23 + #(1 2 3 4) + ;; (osc::encode-timetag :now) + ))))) + +(test v1.1-keyword-typetags + "OSC typetag encoding test." + (is (equalp + (osc::encode-typetags '(:true :false :null :impulse)) + #(44 84 70 78 73 0 0 0)))) + +;; (osc::encode-typetags '("s" 1)) + + ;; play nicely with others (in-suite interoperability) diff --git a/osc.lisp b/osc.lisp index f48b596..37a6d48 100644 --- a/osc.lisp +++ b/osc.lisp @@ -82,29 +82,43 @@ (defun encode-typetags (data) "Create 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. .. + valid typetags according to the OSC 1.0 spec are ,i ,f ,s and ,b + the OSC 1.1 spec includes ,h ,t ,d ,S ,T ,F ,N and ,I - NOTE: currently handles the following tags - i => #(105) => int32 - f => #(102) => float32 - s => #(115) => string - b => #(98) => blob - h => #(104) => int64 - and considers non int/float/string data to be a blob." + The following tags are written based on type check + integer => i => #(105) + => h => #(104) + single-float => f => #(102) + double-float => d => #(100) + simple-string => s => #(115) + * => b => #(98) + The following tags are written based on :keywords in the data + :true (or t) => T => #(84) + :false => F => #(70) + :null => N => #(78) + :impulse => I => #(73) +" (let ((lump (make-array 0 :adjustable t :fill-pointer t))) (macrolet ((write-to-vector (char) `(vector-push-extend (char-code ,char) lump))) - (write-to-vector #\,) + (write-to-vector #\,) ;; #(44) (dolist (x data) (typecase x (integer (if (>= x 4294967296) (write-to-vector #\h) (write-to-vector #\i))) - (float (write-to-vector #\f)) + (single-float (write-to-vector #\f)) + (double-float (write-to-vector #\d)) (simple-string (write-to-vector #\s)) + ;; lisp semantics vs. OSC semantics + (keyword (case x + (:true (write-to-vector #\T)) + (:false (write-to-vector #\F)) + (:null (write-to-vector #\N)) + (:impulse (write-to-vector #\I)))) + (null ('false (write-to-vector #\F))) + ;; anything else is treated as a blob (t (write-to-vector #\b))))) (cat lump (pad (padding-length (length lump)))))) @@ -117,8 +131,10 @@ (dolist (x data) (typecase x (integer (if (>= x 4294967296) (enc encode-int64) (enc encode-int32))) - (float (enc encode-float32)) + (single-float (enc encode-float32)) + (double-float (enc encode-float64)) (simple-string (enc encode-string)) + ;; -> timetag (t (enc encode-blob)))) lump))) @@ -324,24 +340,41 @@ ;; floats are encoded using ieee-floats library for brevity and compatibility ;; - https://ieee-floats.common-lisp.dev/ ;; -;; implementation specific encoding can be used for sbc, cmucl, -;; allegro or ccl if required (see README) +;; It should be possible to use 32 and 64 bit floats in most common lisp environments. +;; An implementation specific encoder/decoder is used where available. (defun encode-float32 (f) "Encode an ieee754 float as a 4 byte vector. currently sbcl/cmucl specific." - (encode-int32 (ieee-floats:encode-float32 f))) + #+sbcl (encode-int32 (sb-kernel:single-float-bits f)) + #+cmucl (encode-int32 (kernel:single-float-bits f)) + #+openmcl (encode-int32 (CCL::SINGLE-FLOAT-BITS f)) + #+allegro (encode-int32 (multiple-value-bind (x y) + (excl:single-float-to-shorts f) + (+ (ash x 16) y))) + #-(or sbcl cmucl openmcl allegro) (encode-int32 (ieee-floats:encode-float32 f))) (defun decode-float32 (v) "Convert a vector of 4 bytes in network byte order into an ieee754 float." - (ieee-floats:decode-float32 (decode-uint32 v))) + #+sbcl (sb-kernel:make-single-float (decode-uint32 v)) + #+cmucl (kernel:make-single-float (decode-int32 v)) + #+openmcl (CCL::HOST-SINGLE-FLOAT-FROM-UNSIGNED-BYTE-32 (decode-uint32 v)) + #+allegro (excl:shorts-to-single-float (ldb (byte 16 16) (decode-uint32 v)) + (ldb (byte 16 0) (decode-uint32 v))) + #-(or sbcl cmucl openmcl allegro) (ieee-floats:decode-float32 (decode-uint32 v))) + (defun encode-float64 (d) "Encode an ieee754 float as a 8 byte vector." - (encode-int64 (ieee-floats:encode-float64 d))) + #+sbcl (cat (encode-int32 (sb-kernel:double-float-high-bits d)) + (encode-int32 (sb-kernel:double-float-low-bits d))) + #-sbcl (encode-int64 (ieee-floats:encode-float64 d))) (defun decode-float64 (v) "Convert a vector of 8 bytes in network byte order into an ieee754 float." - (ieee-floats:decode-float64 (decode-int64 v))) + #+sbcl (sb-kernel:make-double-float + (decode-uint32 (subseq v 0 4)) + (decode-uint32 (subseq v 4 8))) + #-sbcl (ieee-floats:decode-float64 (decode-uint64 v))) ;; osc-strings are unsigned bytes, padded to a 4 byte boundary @@ -355,7 +388,7 @@ (string-padding string))) ;; blobs are binary data, consisting of a length (int32) and bytes which are -;; osc-padded to a 4 byte boundary. +;; padded to a 4 byte boundary. (defun decode-blob (blob) "Decode a BLOB as a vector of unsigned bytes." @@ -371,6 +404,7 @@ ;; NOTE: cannot use (padding-length bl), as it is not the same algorithm. Blobs of 4, 8, 12 etc bytes should not be padded! ;; utility functions for osc-string/padding/slonking +;; NOTE: string padding is treated differently between v1.0 and v1.1 (defun cat (&rest catatac) "Concatenate items into a byte vector."