Schemeベースのcyber-physical programming環境 "Extempore"

Lisp Advent Calendar 2012の記事です。

あまり知られてなさそうなScheme処理系 "Extempore" で遊んでみます。

Extemporeとは

Andrew Sorensen氏によって開発されているOpenSourceのScheme処理系です。
https://github.com/digego/extempore

クロスプラットフォームで動作するよう開発が進められていて、現在はLinux, MacOS X, Windows(いずれも64bitのみ)がサポートされています。

cyber-physical programming って何?

Andrew氏らの提唱する概念で論文がこちら
LiveCoding+αみたいなモンでしょうか。(読んでない

あ、ここでいうLiveCodingっていうのは10日目のnitroさんの記事のソレではなく、英語版Wikipediaにある方の意味。

a performance practice centred upon the use of improvised interactive programming in creating sound and image based digital media.

http://en.wikipedia.org/wiki/Livecoding

要するにプログラミングで行うVJみたいなモンですね。



ExtemporeにはImpromptuという前身があり、Macで動作する無料のソフトでした。(ソースコードは非公開)
3年ほど前にRadium SoftwareのKZRさんが紹介されていたのでご存じの方もいるかもしれません。

Impromptuのデモ(Vimeo)
https://vimeo.com/2735394

動画を見るとわかりますがImpromptuの時点で相当クールな処理系でした。より強力で柔軟な言語機能を得る為に0から処理系を作りなおしたようです。

Extemporeの言語仕様

インタープリタで実行されるSchemeと、LLVMを通してネイティブコードにコンパイルされて実行される見た目Scheme風の静的型付き言語 "xtlang" を組み合わせて処理するハイブリッドなシステムになっています。

;; Schemeのfib
(define (fib n)
  (if (< n 2) 1
      (+ (fib (- n 1))
	 (fib (- n 2)))))

;; xtlangのfib
(bind-func fibx
  (lambda (n:i64)
    (if (< n 2) 1
	(+ (fibx (- n 1))
	   (fibx (- n 2))))))

SchemeはR5RSのサブセットっぽい感じで、継続なんかも使えますがdefine-syntaxがない等いくつか足りない物があるようです。
bind-funcフォームの中身がxtlangです。64bit intを受け取って64bit intを返すコードがllvmコンパイルされ、fibxに束縛されます。

文法他詳しい説明はこちらにあります。

言語機能以外ではDSP(Digital Sound Processing)機能とOpenGLを使ったグラフィクス描画の機能が用意されていますが、FFI機構があるのでほぼなんでもできる感じです。
例. Kinectから入力を読み取るライブラリ

Extemporeのビルド

LinuxMacについてはgithubレポジトリのドキュメントの通りに作業すればOK。
Windowsの場合はVS2010製品版でビルドする場合はドキュメントの通りでOKですが2012の場合はLinuxMac共通のLLVMパッチの他にいくつか修正しないといけない作業があります。
http://benswift.me/2012-11-05-building-extempore-on-windows.html
VS2012の場合↑このページの説明の通りに作業すればOK。
ちなみにVS2010expressだとCMAKEがうまく動かなくてLLVMのビルドでハマります…2012ならexpressでも大丈夫。

また、BoostとかLLVMにパッチ合っててフルビルドとかめんどくせーって人用にLinuxとMac OSX用のコンパイル済みバイナリが用意されています。
https://github.com/digego/extempore/downloads
Githubのdownloadsページはつい先日廃止が決まったので欲しい人はお早めに。
Windows用バイナリは公式で配布されてませんが、私がビルドした物をDropboxに置いておきました。

32bit環境はサポートされてないんですが、音鳴らすだけならとりあえず大丈夫なんで置いておきます。
FFI関係の機能が全滅なのでOpenGLのサンプルとか動かそうとすると死にます。

Extemporeの起動

Extemporeはファイルを渡して実行するようなインタプリタではなく、SWANKの様なS式サーバーとして動作します。
引数なしで起動すると、ポート7099でTCP接続を待ちます。

##########################################
##                                      ##
##               EXTEMPORE              ##
##                                      ##
##           andrew@moso.com.au         ##
##                                      ##
##            (c) 2010-2012             ##
##                                      ##
##########################################
     ################################
          ######################
               ############
                    ##

// 中略 //

Starting primary process
Trying to connect to 'localhost' on port 7099
New client connection
Successfully connected to remote process

Starting utility process
Trying to connect to 'localhost' on port 7098
New client connection
Successfully connected to remote process

データの送受信は評価したいS式をそのまま送って最後にCRLFを付ければ良いようです。
レポジトリにEmacs用とVim用のプラグインがありますが、今回はEmacsを使って行きます。

extempore-modeの使い方

extempore-modeの設定方法は省略。(elファイルを見て下さい)
操作は

  • C-x C-j … Extemporeに接続
  • C-x C-x … カーソル位置のフォーム(トップレベルまで)をExtemporeに送信
  • C-x C-r … リージョン内のフォームをExtemporeに送信
  • C-x C-b … バッファ内のすべてのフォームをExtemporeに送信

http://gyazo.com/c31ba4179eb6d316c31039c70e7fb687.png
左がソースバッファ(extempore-mode)で右がshell-modeから起動したextemporeの出力
左側のカーソル位置(緑の□)で C-x C-x を押すと、 (define (fib ... )) が評価されます。
print, display関数等の出力は標準出力へ、フォームの評価結果の値はminibufferに表示されます。
http://gyazo.com/b3fb6df5f58e16d5c4ba734c8dab6a7e.png

先ほどの2種のフィボナッチ関数を走らせてみて、どのくらい差があるか見てみましょう。

(define-macro (time form)
  `(let ((t (clock:clock))
	 (result ,form))
     (print (- (clock:clock) t) "sec.\n")
     result))

(time (println (fib 30)))
(time (println (fibx 30)))
(time (println (fibx 40)))

結果

1346269
20.868164 sec.

1346269
0.072266 sec.

165580141
9.282226 sec.

私のマシンでは(fib 30)はGaucheで0.5秒くらいですからxtlangはかなり速いですね。sbclの最適化ナシの速度と大体同じくらいになっています。
しかし実際に触ってみるとわかるのですがコンパイルがとても重いです。
ですので重い処理はあらかじめロード(コンパイル)しておいて、Schemeで各々の処理を組み合わせてLiveCodingしていく、というスタイルになります。

音を鳴らしてみる

前述のとおり実行速度はとても速いので、MIDI等使って外部のシンセサイザーを鳴らすまでもなくxtlangで波形生成からエフェクト処理まで書けてしまいます。
このサンプルを実行してみると、こんな音がします、と紹介しようと思ったんですがUSB音源がダメすぎてループバック録音ができない事に気づいた午後11時。両オスのコードも見つからないぞ。ぎゃー


(12/28 追記) 録音できた

竹内関数音楽をExtemporeで

昨年話題になった竹内関数音楽をExtemporeで演奏するコードを書いたのですがこれも録音できず。
折角なのでコードだけ載せておきます。録音は後日…


(12/28 追記) こっちも録音できた

;; 音色はさっきのサンプルから拝借
(load "libs/core/instruments.xtm")

(define-instrument synth synth_note_c synth_fx)

(bind-func dsp:[double,double,double,double,double*]*
  (lambda (in time chan dat)
    (cond ((< chan 2.0)
           (synth in time chan dat))
          (else 0.0))))

(dsp:set! dsp)


;; 音階
(define (scale n base)
  (let ((m (modulo n 7)))
    (+ base
       (* 12 (/ (- n m) 7))
       (list-ref '(0 2 4 5 7 9 11) m))))

;; tak関数
(define (tak x y z next)
  (let* ((len (* 2 44100))   ; 2秒ずつ鳴らす
         (t (+ (now) len)))  ; 次の音へ移る時刻
    (print "[" x y z "]\n")
    ; 和音発声
    (map (lambda (n) (play-note (now) synth (scale n 60) 70 len))
         (list x y z))
    ; 次の音へ
    (if (<= x y)
        (callback t next y)
      (callback t tak (- x 1) y z
                (lambda (nx)
                  (tak (- y 1) z x
                       (lambda (ny)
                         (tak (- z 1) x y
                              (lambda (nz)
                                (tak nx ny nz next))))))))))

;; start
(tak 10 5 1 println)

;; stop
(define (tak x y z next) )

tak関数がCPS変換されている理由ですが、普通の形だと発音の後に発音時間分待ってから(※)次の再帰呼び出しへ行かないといけないので、途中で演奏を止めたくなったらC-c C-cで強制終了するしかありません。
というかそもそも処理をブロックしてしまったらLiveCodingどころではないので、再帰的な演奏はcallback機構を使って "Temporal Recursion" というスタイルで書くのがExtempore流です。

http://benswift.me/2012-10-15-time-in-extempore.html
↑この辺参照

(tak 10 5 1 println)を評価するとcallbackで演奏を継続しつつ入力も受け付ける状態になります。
stopコメントの式を評価すると、takが何もしない関数に上書きされて大体1フレーズ後に演奏が止まります。

※ play-noteによる発音自体は非同期処理

Extempore動画

Andrew氏がVimeoに投稿しているモノをいくつかご紹介。とても面白いモノばかりですので是非一度ご覧あれ。

チュートリアル。ちょっとxtlangの書き方が今と違いますがExtemporeとはどんな物なのかがよくわかります。

YAMAHAの自動演奏ピアノ"disklavier"をExtemporeからコントロールして演奏。

3Dモデルを動かすサンプル。

Extemporeでモーションキャプチャーからの入力を読み取って音を鳴らす実験。これはKinectではない模様。

複数の巨大タッチパネルの入力&物理演算。

最新作。楽しそー。

Impromptuの動画ですが、未来のIDEっぽくてステキ。

映像をリアルタイム解析してBGMを自動生成するシステム。(ImpromptuかExtemporeかどっちか分からず)

Impromptuでのクリスマス風音楽の即興演奏。



以上Extemporeの紹介でした。
音鳴らすだけならそんなに難しくないのでみなさんも是非遊んでみて下さい。