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*)

Sourceforge project page