Common Lisp + DataRacketで音を使った作品を作る

Data Racket | Ethno Tekhを使用すれば解析された音声データをOSC(Open Sound Control)で簡単に受け取ることができます。

Common Lispにはcl-oscというOSCをデコードするライブラリがあります。OSC自体はUDPプロトコルなので、UDP通信するためのコードを書かなくてはなりません。cl-oscのサンプルではsbclに依存したコードが載っています。僕が使っていた処理系はClozureCLなので、処理系ポータブルな通信ライブラリusocketを使用してOSCを受信するコードを紹介します。

usocketを用いてUDP通信を行うサンプルコードを書いている方がいたので、参考にさせてもらいました。
Short guide to UDP/IP Client/Server programming in Common Lisp using usockets · GitHub

OSCを受信するサンプル

(ql:quickload '(:usocket :osc))

(defparameter *port* 9000)

(defun create-server (port)
  (usocket:socket-connect nil nil
			  :protocol :datagram
			  :element-type '(unsigned-byte 8)
			  :local-host "127.0.0.1"
			  :local-port port))

(defparameter *osc-sock*
  (create-server *port*))

(defun start-receiving (socket buffer)
  (format t "start server~%")
  (unwind-protect
      (multiple-value-bind (buffer size client received-port)
	  (loop do
		(usocket:socket-receive socket buffer 1024)
		(let ((message (osc:decode-bundle buffer)))
		  (format t "received -=>~S~%" (osc:decode-bundle buffer))
		  (if (string= (car message) "stop") (return)))))
    (usocket:socket-close socket)
    (format t "exit~%")))

(start-receiving *osc-sock*
		 (make-array 1024 :element-type '(unsigned-byte 8)))

起動して、DataRacketからOSCが入ってくると以下のように出力されます。

received -=>("/audio/bright" 0.017748356)
received -=>("/audio/noise" 0.32115465)
received -=>("/audio/loud" 0.43585107)
received -=>("/audio/fft" 0.31239712 0.270671 0.5963343 0.5104574 0.4887981 0.5261438 0.55091 0.21614692 0.20689762 0.17976543 0.08880567 0.13944802 0.124899484 0.12268175 0.13263442 3.1389509E-4 0.045346215 0.031639766 0.014442262 0.008829511 0.025995951 0.012009757 0.0 0.0 0.0)

cl-oscの仕事はdecode-bundleでデコードすることのみです。
気になるのが終了時の処理ですね。外側から

(usocket:socket-close *osc-sock*)

を呼び出してやるとエラーが発生してしまいます。以下のようにサーバーを止めるクライアントを作成し、stopメッセージを送る関数を作成してみました。

(defun send-stop-msg (port)
  (usocket:with-client-socket (sock nil
				    "127.0.0.1"
				    port
				    :protocol :datagram
				    :element-type '(unsigned-byte 8))
    (let ((stop-msg (osc:encode-message "stop")))
      (usocket:socket-send sock stop-msg (length stop-msg))
      (usocket:socket-close sock))))

実践

これでCommon Lispで音に反応するプログラムを作る準備が整いましたね。
今回はcl-glutを利用した、以下のような簡単なサンプルを用意してみました。
DataRacket + Common Lispなサンプル · GitHub

www.youtube.com

cl-glut-examplesのglut-teapotを改造したもので、teapotの大きさが音に合わせて変わります。マウスドラッグで回転します。サーバーはrun関数が呼び出される際に起動され、ウィンドウが閉じられた際に終了するようになっています。

windows8.1、64bitのClozureCL64bitで動作確認しています。恐らくスレッド対応している処理系なら動くはず。windowsの場合、処理系のbitに合わせたfreegluit.dllが必要です。

DataRacketの使い方は、起動した後に適切な設定をするだけです。windowsの場合、録音デバイスをステレオミキサー等に設定し、DataRacket側のdeviceも適切に設定します。僕の場合、devicead_portaudio Windows DirectSoundに設定すると音をとってくれました。

パラメータをいじる

OSCが受信されるたびに、loudからティーポットの大きさ、FFT結果から色のパラメータを変化させています。

(defun process-msg (message)
  (if (string= "/audio/loud" (car message)) (setf *scale* (+ 0.2 (* 3 (second message)))))
  (if (string= "/audio/fft" (car message))
      (progn
	(setf *diffuse-r* (+ 0.3 (* 2 (second message))))
	(setf *diffuse-g* (+ 0.3 (* 2 (elt message 5))))
	(setf *diffuse-b* (+ 0.3 (* 2 (elt message 10)))))))

上記のprocess-msg関数あたりをいじくりまわしてみると面白いのではないでしょうか。実行しつつパラメータを動かした結果を見ることができるので非常に楽しいですね。

おわりに

実際に作ってみるとわかるのですが、取得した値をどう利用するのかというのはとても難しいです。キック等のパターンはある程度とれたりしますが、正確に音と同期させるのは難しく、アクセント程度にしかならない場合も多いです。この辺はBPM検出したりすると良いという話など色々ありますが、製作者の工夫次第でしょうね。