synchroscope (part 3)

This commit is contained in:
nik gaffney 2024-01-02 00:38:25 +01:00
parent 7d4ba661e6
commit 2bad195b15
Signed by: nik
GPG key ID: 989F5E6EDB478160
3 changed files with 83 additions and 68 deletions

View file

@ -22,9 +22,9 @@ This implementation supports the [[https://opensoundcontrol.stanford.edu/spec-1_
| *Type tag* | *type* | *description* | *v1.0* | *v1.1* | *cl-osc* | | *Type tag* | *type* | *description* | *v1.0* | *v1.1* | *cl-osc* |
| i | int32 | 32-bit big-endian twos complement integer | *R* | *R* | YES | | i | int32 | 32-bit big-endian twos complement integer | *R* | *R* | YES |
| f | float32 | 32-bit big-endian IEEE 754 floating point number | *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 | | | | 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 | | | | 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* | | | T | True | No bytes are allocated in the argument data. | O | *R* | |
| F | False | 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* | | | 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* | | | t | OSC-timetag | an OSC timetag in NTP format, encoded in the data section | O | *R* | |
| h | int64 | 64 bit big-endian twos complement integer | O | O | YES | | h | int64 | 64 bit big-endian twos 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 | | 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 | | | c | | an ascii character, sent as 32 bits | O | O | |
| r | | 32 bit RGBA color | 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? | | ] | | 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= - 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

View file

@ -66,6 +66,9 @@
(is (equalp (is (equalp
(osc::decode-float32 #(254 254 254 254)) -1.6947395e38))) (osc::decode-float32 #(254 254 254 254)) -1.6947395e38)))
;; (osc::decode-float32 #(127 255 255 255))
;; #<SINGLE-FLOAT quiet NaN>
(test osc-string (test osc-string
"OSC string encoding tests." "OSC string encoding tests."
(is (equalp (is (equalp
@ -290,6 +293,28 @@
;; v1.1. tests ;; v1.1. tests
(in-suite protocol-v1.1) (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 ;; play nicely with others
(in-suite interoperability) (in-suite interoperability)

View file

@ -82,29 +82,43 @@
(defun encode-typetags (data) (defun encode-typetags (data)
"Create a typetag string suitable for the given DATA. "Create a typetag string suitable for the given DATA.
valid typetags according to the OSC spec are ,i ,f ,s and ,b valid typetags according to the OSC 1.0 spec are ,i ,f ,s and ,b
non-std extensions include ,{h|t|d|S|c|r|m|T|F|N|I|[|]} the OSC 1.1 spec includes ,h ,t ,d ,S ,T ,F ,N and ,I
see the spec for more details. ..
NOTE: currently handles the following tags The following tags are written based on type check
i => #(105) => int32 integer => i => #(105)
f => #(102) => float32 => h => #(104)
s => #(115) => string single-float => f => #(102)
b => #(98) => blob double-float => d => #(100)
h => #(104) => int64 simple-string => s => #(115)
and considers non int/float/string data to be a blob." * => 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 (let ((lump (make-array 0 :adjustable t
:fill-pointer t))) :fill-pointer t)))
(macrolet ((write-to-vector (char) (macrolet ((write-to-vector (char)
`(vector-push-extend `(vector-push-extend
(char-code ,char) lump))) (char-code ,char) lump)))
(write-to-vector #\,) (write-to-vector #\,) ;; #(44)
(dolist (x data) (dolist (x data)
(typecase x (typecase x
(integer (if (>= x 4294967296) (write-to-vector #\h) (write-to-vector #\i))) (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)) (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))))) (t (write-to-vector #\b)))))
(cat lump (cat lump
(pad (padding-length (length lump)))))) (pad (padding-length (length lump))))))
@ -117,8 +131,10 @@
(dolist (x data) (dolist (x data)
(typecase x (typecase x
(integer (if (>= x 4294967296) (enc encode-int64) (enc encode-int32))) (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)) (simple-string (enc encode-string))
;; -> timetag
(t (enc encode-blob)))) (t (enc encode-blob))))
lump))) lump)))
@ -324,24 +340,41 @@
;; floats are encoded using ieee-floats library for brevity and compatibility ;; floats are encoded using ieee-floats library for brevity and compatibility
;; - https://ieee-floats.common-lisp.dev/ ;; - https://ieee-floats.common-lisp.dev/
;; ;;
;; implementation specific encoding can be used for sbc, cmucl, ;; It should be possible to use 32 and 64 bit floats in most common lisp environments.
;; allegro or ccl if required (see README) ;; An implementation specific encoder/decoder is used where available.
(defun encode-float32 (f) (defun encode-float32 (f)
"Encode an ieee754 float as a 4 byte vector. currently sbcl/cmucl specific." "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) (defun decode-float32 (v)
"Convert a vector of 4 bytes in network byte order into an ieee754 float." "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) (defun encode-float64 (d)
"Encode an ieee754 float as a 8 byte vector." "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) (defun decode-float64 (v)
"Convert a vector of 8 bytes in network byte order into an ieee754 float." "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 ;; osc-strings are unsigned bytes, padded to a 4 byte boundary
@ -355,7 +388,7 @@
(string-padding string))) (string-padding string)))
;; blobs are binary data, consisting of a length (int32) and bytes which are ;; 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) (defun decode-blob (blob)
"Decode a BLOB as a vector of unsigned bytes." "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! ;; 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 ;; utility functions for osc-string/padding/slonking
;; NOTE: string padding is treated differently between v1.0 and v1.1
(defun cat (&rest catatac) (defun cat (&rest catatac)
"Concatenate items into a byte vector." "Concatenate items into a byte vector."