Open Sound Control messages |
---|
We can set the configuration variables *OSC-BUFFER-SIZE*
and *OSC-MAX-VALUES*
in ${HOME}/.incudinerc
to specify the size of the OSC buffer in bytes and the max number of
the values to send/receive. Alternatively, we can set the keywords
:BUFFER-SIZE
and :MAX-VALUES
of
OSC:OPEN
, or the variables OSC:*BUFFER-SIZE*
and OSC:*MAX-VALUES*
before to call OSC:OPEN
.
(in-package :scratch) ;; Default values (changed 2017-05-24) SCRATCH> osc:*buffer-size* 1500 SCRATCH> osc:*max-values* 50
The faster way to send a OSC message is
SCRATCH> (defvar *oscout* (osc:open :direction :output)) SCRATCH> (osc:message *oscout* "/osc/test" "isf" 1 "two" 3.0f0)
where host, port and protocol are "localhost", 32126 (aka #36ROSE)
and :UDP
by default.
It's enlightening to show the expansion of the OSC:MESSAGE
compiler-macro:
(funcall (compiler-macro-function 'osc:message) '(osc:message *oscout* "/osc/test" "isf" 1 "two" 3.0f0) nil) (LET ((#:S505 *OSCOUT*)) (INCUDINE.OSC:START-MESSAGE #:S505 "/osc/test" "isf") (INCUDINE.OSC::SET-VALUE #:S505 0 1) (INCUDINE.OSC::SET-VALUE #:S505 1 "two") (INCUDINE.OSC::SET-VALUE #:S505 2 3.0f0) (INCUDINE.OSC:SEND #:S505))
OSC:START-MESSAGE
writes the OSC address pattern and
the OSC type tag on the stream buffer, then it updates the pointers
to the required values. These pointers allow to directly change a
value within the buffer to send. It's simple and fast to set a value
and send another message:
SCRATCH> (setf (osc:value *oscout* 0) -123) SCRATCH> (osc:send *oscout*)
Done. The interface is the same with TCP and/or SLIP, besides the conversion of the values to network byte order is implicit on little-endian machines.
The previous example in slow motion plus other utilities:
SCRATCH> (osc:start-message *oscout* "/osc/test" "isf") 32 SCRATCH> (osc:address-pattern *oscout* t) "/osc/test" "isf" SCRATCH> (loop for i below (osc:required-values *oscout*) collect (osc:value *oscout* i)) (0 "" 0.0) SCRATCH> (setf (osc:value *oscout* 0) 1) SCRATCH> (setf (osc:value *oscout* 1) "two") SCRATCH> (setf (osc:value *oscout* 2) 3.0f0) SCRATCH> (loop for i below 3 collect (osc:value *oscout* i)) (1 "two" 3.0) SCRATCH> (osc:send *oscout*) 32
Note: explicit coercing is necessary with (SETF OSC:VALUE)
SCRATCH> (setf (osc:value *oscout* 2) 3) ; wrong SCRATCH> (osc:value *oscout* 2) 0.0 SCRATCH> (setf (osc:value *oscout* 2) 3.0f0) ; correct 3.0
Memo: 3.0
is not a single-float value if *READ-DEFAULT-FLOAT-FORMAT*
is DOUBLE-FLOAT
OSC:VALUE-POINTER
returns the pointer to a required value:
SCRATCH> (cffi:foreign-string-to-lisp (osc:value-pointer *oscout* 1)) "two" 3
However, on little-endian machines:
SCRATCH> (cffi:mem-ref (osc:value-pointer *oscout* 0) :int32) 16777216 SCRATCH> (swap-bytes:ntohl (cffi:mem-ref (osc:value-pointer *oscout* 0) :uint32)) 1
OSC:BUFFER-TO-OCTETS
returns the octets of the message:
SCRATCH> (osc:buffer-to-octets *oscout*)
#(47 111 115 99 47 116 101 115 116 0 0 0 44 105 115 102 0 0 0 0
0 0 0 1 116 119 111 0 64 64 0 0)
32
We can store the message in an existent vector (with optional START/END positions):
SCRATCH> (let ((a (make-array (osc:message-length *oscout*) :element-type '(unsigned-byte 8)))) (osc:buffer-to-octets *oscout* a)) ;; We'll use it. SCRATCH> (defvar *msg1* (osc:buffer-to-octets *oscout*))
The following example shows a simple and fast way to control a parameter of an external synth:
SCRATCH> (osc:start-message *oscout* "/synth/test" "f") 20 SCRATCH> (set-rt-block-size 1) SCRATCH> (rt-start) (defun synth-test (time) (setf (osc:value *oscout* 0) (+ 100 (random 1000.0f0))) (osc:send *oscout*) (aat (+ time #[1/5 sec]) #'synth-test it)) SCRATCH> (rt-eval () (synth-test (now))) ;; game over SCRATCH> (flush-pending) SCRATCH> (rt-stop)
We can reuse the octets stored in *MSG1*
:
SCRATCH> (osc:octets-to-buffer *msg1* *oscout*) SCRATCH> (osc:send *oscout*)
The pointers to the values are not updated after
OSC:OCTETS-TO-BUFFER
:
SCRATCH> (osc:value *oscout* 0) 0.0 ; wrong
However, OSC:OCTETS-TO-BUFFER
sets a internal flag to inform
OSC:INDEX-VALUES
about the necessity to update the pointers:
SCRATCH> (osc:index-values *oscout*) SCRATCH> (osc:value *oscout* 0) 1 ; correct SCRATCH> (setf (osc:value *oscout* 1) "two-fold") SCRATCH> (osc:send *oscout*) 40
Sending an OSC message with timestamp:
SCRATCH> (osc:simple-bundle *oscout* .5 "/osc/test" "iii" 1 2 3) 52 SCRATCH> (setf (osc:latency *oscout*) .2) ;; time = latency + 1.5 seconds SCRATCH> (osc:simple-bundle *oscout* 1.5 "/osc/test" "iii" 1 2 3) SCRATCH> (setf (osc:latency *oscout*) 0) ;; Send the previous OSC bundle immediately. SCRATCH> (osc:send-bundle *oscout*) ;; Send again with delta time 0.85 seconds. SCRATCH> (osc:send-bundle *oscout* .85)
TIMESTAMP
returns a double float value for the current
time of day in universal time format:
SCRATCH> (timestamp)
3.704630902676296d9
If OSC:SIMPLE-BUNDLE
is called with no values, it prepares
the OSC message but doesn't send it. The previous example is equivalent to
SCRATCH> (osc:simple-bundle *oscout* 0 "/osc/test" "iii") 0 SCRATCH> (setf (osc:value *oscout* 0) 1) SCRATCH> (setf (osc:value *oscout* 1) 2) SCRATCH> (setf (osc:value *oscout* 2) 3) SCRATCH> (osc:send-bundle *oscout* .5) 52
We don't send OSC bundles in the past, therefore we can use the time with dual meaning: if it is greater than 63103 seconds (about 17 hours), the time is absolute otherwise it is added to the current time.
63104 is the offset of the NTP Timestamp Era 1 (from 8 Feb 2036), so this hack will work for centuries.
Sending three OSC bundles with a time related to a common absolute time:
SCRATCH> (let ((time (timestamp))) (osc:simple-bundle *oscout* time "/osc/test" "iii" 1 2 3) (setf (osc:value *oscout* 1) 123) (osc:send-bundle *oscout* (+ time .5)) (setf (osc:value *oscout* 2) 321) (osc:send-bundle *oscout* (+ time 1.5)))
OSC:SEND-BUNDLE
works after OSC:OCTETS-TO-BUFFER
SCRATCH> (setq *msg1* (osc:buffer-to-octets *oscout*)) SCRATCH> (osc:message *oscout* "/test/synth/freq" "f" 440.0f0) 28 SCRATCH> (osc:octets-to-buffer *msg1* *oscout*) SCRATCH> (osc:send-bundle *oscout* .75) 52
and OSC:SEND
works after OSC:SIMPLE-BUNDLE
...
SCRATCH> (osc:simple-bundle *oscout* 1.2 "/test/synth/freq" "f" 440.0f0) 48 SCRATCH> (osc:send *oscout*) 28
...but OSC:SEND-BUNDLE
fails after OSC:MESSAGE
if the message length is changed
SCRATCH> (osc:message *oscout* "/test/msg" "iii" 1 2 3) 32 SCRATCH> (osc:send-bundle *oscout*) ; SERVER ERROR 48 instead of 52 (truncated message)
See OSC:BUNDLE to send an OSC bundle with two or more OSC messages.
Broadcast in a local network:
SCRATCH> (osc:close *oscout*) SCRATCH> (setf *oscout* (osc:open :host "192.168.0.255" :direction :output)) SCRATCH> (setf (osc:broadcast *oscout*) t) SCRATCH> (osc:message *oscout* "/osc/test" "iii" 1 2 3) SCRATCH> (osc:close *oscout*)
The simplest way to receive a OSC message is the following:
SCRATCH> (defvar *oscin* (osc:open)) SCRATCH> (recv-start *oscin*) #<RECEIVER INCUDINE.OSC:INPUT-STREAM RUNNING> SCRATCH> (make-osc-responder *oscin* "/osc/test" "iii" (lambda (a b c) (msg warn "~D ~D ~D" a b c)))
and now we can send OSC messages "/osc/test" with three
int32
to our server (the port is always the default
#36ROSE).
Again, the expansion of the MAKE-OSC-RESPONDER
macro
shows useful details:
(macroexpand-1 '(make-osc-responder *oscin* "/osc/test" "iii" (lambda (a b c) (msg warn "~D ~D ~D" a b c)))) (MAKE-RESPONDER *OSCIN* (LAMBDA (#:S582) (WHEN (INCUDINE.OSC:CHECK-PATTERN #:S582 "/osc/test" "iii") (INCUDINE.OSC:WITH-VALUES (A B C) (#:S582 "iii") (MSG WARN "~D ~D ~D" A B C))) (VALUES)))
The expansion points out two new utilities. OSC:CHECK-PATTERN
returns T
if the OSC address pattern and the type tag are "/osc/test" and "iii"
respectively:
SCRATCH> (osc:check-pattern *oscin* "/osc/test" "iif") NIL SCRATCH> (osc:check-pattern *oscin* "/osc/tes" "iii") NIL SCRATCH> (osc:check-pattern *oscin* "/osc/test" "iii") T
If the OSC address pattern is a regexp, you could use
cl-ppcre
with the strings returned by OSC:ADDRESS-PATTERN
.
OSC:WITH-VALUES
is really useful to get the received
values. Under the hood:
(macroexpand-1 '(incudine.osc:with-values (a b c) (*oscin* "iii") (msg warn "~d ~d ~d" a b c))) (LET ((#:%STREAM522 *OSCIN*)) (WHEN (> 3 (INCUDINE.OSC::STREAM-MAX-VALUES #:%STREAM522)) (INCUDINE::NETWORK-ERROR "The length of the OSC type tag is ~D but the limit ~%~ for this OSC:STREAM is ~D" 3 (INCUDINE.OSC::STREAM-MAX-VALUES #:%STREAM522))) (INCUDINE.OSC:INDEX-VALUES #:%STREAM522 NIL T) (SYMBOL-MACROLET ((A (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:%STREAM522 2) :INT32)) (B (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:%STREAM522 3) :INT32)) (C (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:%STREAM522 4) :INT32))) (MSG WARN "~D ~D ~D" A B C)))
Excellent! It calls OSC:INDEX-VALUES
with the option to
force the conversion from network byte order on little-endian machines,
then we get the values with automatic coercing.
If we use OSC:WITH-VALUES
or OSC:INDEX-VALUES
in other responders, OSC:INDEX-VALUES
is smart because it
updates the pointers only once after the reception of a OSC message.
(SETF BUS)
is faster than SET-CONTROL
to
change a parameter of a DSP. For example:
SCRATCH> (remove-all-responders *oscin*) SCRATCH> (setf (bus 0) (sample 1)) SCRATCH> (make-osc-responder *oscin* "/bplay/rate" "f" (lambda (r) (barrier (:memory) (setf (bus 0) (sample r))))) (dsp! bplay ((buf buffer)) (foreach-channel (cout (buffer-play buf (bus 0) 0 t #'free)))) SCRATCH> (defvar *loop1* (buffer-load "loop1.wav")) SCRATCH> (rt-start) SCRATCH> (bplay *loop1*) ;;; Start to send OSC messages with address "/bplay/rate". ;;; THE END SCRATCH> (free 0) SCRATCH> (rt-stop) ;; Note: REMOVE-RECEIVER-AND-RESPONDERS in OSC:*BEFORE-CLOSE-HOOK* SCRATCH> (osc:close *oscin*)
Getting Started with Incudine | Home |