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*
SCRATCH> osc:*max-values*

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

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

SCRATCH> (osc:address-pattern *oscout* t)

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

Note: explicit coercing is necessary with (SETF OSC:VALUE)

SCRATCH> (setf (osc:value *oscout* 2) 3)    ; wrong
SCRATCH> (osc:value *oscout* 2)

SCRATCH> (setf (osc:value *oscout* 2) 3.0)  ; correct

OSC:VALUE-POINTER returns the pointer to a required value:

SCRATCH> (cffi:foreign-string-to-lisp (osc:value-pointer *oscout* 1))

However, on little-endian machines:

SCRATCH> (cffi:mem-ref (osc:value-pointer *oscout* 0) :int32)

SCRATCH>  (swap-bytes:ntohl (cffi:mem-ref (osc:value-pointer *oscout* 0)

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)

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

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

Sending an OSC message with timestamp:

SCRATCH> (osc:simple-bundle *oscout* .5 "/osc/test" "iii" 1 2 3)

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)

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


SCRATCH> (setq *msg1* (osc:buffer-to-octets *oscout*))
SCRATCH> (osc:message *oscout* "/test/synth/freq" "f" 440.0)
SCRATCH> (osc:octets-to-buffer *msg1* *oscout*)
SCRATCH> (osc:send-bundle *oscout* .75)

and OSC:SEND works after OSC:SIMPLE-BUNDLE...

SCRATCH> (osc:simple-bundle *oscout* 1.2 "/test/synth/freq" "f" 440.0)
SCRATCH> (osc:send *oscout*)

...but OSC:SEND-BUNDLE fails after OSC:MESSAGE

SCRATCH> (osc:message *oscout* "/test/msg" "iii" 1 2 3)
SCRATCH> (osc:send-bundle *oscout*)  ; SERVER ERROR
48 instead of 52 (truncated message)

Broadcast in a local network:

SCRATCH> (osc:close *oscout*)
SCRATCH> (setf *oscout* (osc:open :host "" :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*)

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

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")
SCRATCH> (osc:check-pattern *oscin* "/osc/tes" "iii")
SCRATCH> (osc:check-pattern *oscin* "/osc/test" "iii")

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*))
                     (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:STREAM875 2)
                     (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:STREAM875 3)
                     (CFFI:MEM-REF (INCUDINE.OSC::ARG-POINTER #:STREAM875 4)
    (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))
    (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".

SCRATCH> (free 0)
SCRATCH> (rt-stop)
SCRATCH> (osc:close *oscin*)

Sourceforge project page