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.0)

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 macro:

(macroexpand-1 '(osc:message *oscout* "/osc/test" "isf" 1 "two" 3.0))
(PROGN
 (INCUDINE.OSC:START-MESSAGE *OSCOUT* "/osc/test" "isf")
 (INCUDINE.OSC::SET-VALUE *OSCOUT* 0 1)
 (INCUDINE.OSC::SET-VALUE *OSCOUT* 1 "two")
 (INCUDINE.OSC::SET-VALUE *OSCOUT* 2 3.0)
 (INCUDINE.OSC:SEND *OSCOUT*))

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.0)
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.0)  ; correct
3.0

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 follow 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.0)))
  (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

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 follow:

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 ((#:STREAM875 *OSCIN*))
  (INCUDINE.OSC:INDEX-VALUES #:STREAM875 NIL T)
  (SYMBOL-MACROLET ((A
                     (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:STREAM875 2)
                                   :INT32))
                    (B
                     (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:STREAM875 3)
                                   :INT32))
                    (C
                     (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:STREAM875 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 one time 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*)

Sourceforge project page