ジャバでMIDIコントローラを扱うメモ
最近VJ的なパフォーマンス用ソフトを作ったりしていたのですが、その過程でMIDIコントローラを使いたいと思い、評判が良くて、手ごろな値段のKorg nanoKONTROL2を購入しました。
ジャバは標準でMIDIが扱えますが、MIDIコントローラを扱う情報が少なく、見つけづらかったのでメモしておきます。
ジャバのMIDI関連に関しては以下のブログエントリが詳しいです。
http://blog.bonar.jp/entry/20090322/1237711377
このエントリでは自作Receiverを定義して、MIDIキーボードから受け取ったMIDIの演奏情報にフィルターをかけるようなことをしているみたいですね。
このサイトだけで大体は分かったのですが、コントローラにReceiverを登録する際にひっかかりました。僕の環境では以下のような機器が検出されていたのですが、僕は最初、External MIDI Portの方に接続しようとして失敗していました。nanoKONTROL2が二つ検出されているのがよくわかりませんが、External MIDI Portの方にはReceiverを登録できないみたいですね。
Gervill -- Software MIDI Synthesizer nanoKONTROL2 -- No details available Microsoft MIDI Mapper -- Windows MIDI_MAPPER Microsoft GS Wavetable Synth -- Internal software synthesizer nanoKONTROL2 -- External MIDI Port Real Time Sequencer -- Software sequencer
とりあえず以下のstackoverflowの回答のコードを使えば検出され、使用できるMIDI機器全てにレシーバーを追加することができます。
http://stackoverflow.com/questions/6937760/java-getting-input-from-midi-keyboard
検出されたMIDI信号はReceiverのsend関数で処理されます。僕はとりあえず以下のような関数を用意しました
public void send(MidiMessage message, long timeStamp) { if (message instanceof ShortMessage) { ShortMessage sm = ((ShortMessage)message); switch(sm.getCommand()) { case ShortMessage.CONTROL_CHANGE: System.out.println(sm.getChannel()); System.out.println(sm.getData1()); System.out.println(sm.getData2()); break; case ShortMessage.NOTE_ON: System.out.println("note on"); break; default: System.out.println("default"); break; } } System.out.println("midi received"); }
ShortMessageは信号の種類ですが、MIDIコントローラでは全てCONTROL_CHANGEになるみたいです。getData1でMIDIコントローラにおけるつまみやボタンのid、getData2でコントローラの0から127までの値が取得できます。
ちなみにボタンは押されたときに127、指が離されたときに0が返るようになっています。
残念なことにidからはどれがつまみでどれがスライダーかはわからないので自分で調べる
必要があるみたいですね。
nanoKONTROL2に関しては自分で調べて以下のようなEnumを定義しておきました。
このIDをキーにして処理を呼び出すようにすればよいのではないでしょうか。
public interface MidiController { public int getId(); } public enum KorgNanoControl2 implements MidiController{ SLIDER1(0), SLIDER2(1), SLIDER3(2), SLIDER4(3), SLIDER5(4), SLIDER6(5), SLIDER7(6), SLIDER8(7), KNOB1(16), KNOB2(17), KNOB3(18), KNOB4(19), KNOB5(20), KNOB6(21), KNOB7(22), KNOB8(23), BUTTON_S1(32), BUTTON_S2(33), BUTTON_S3(34), BUTTON_S4(35), BUTTON_S5(36), BUTTON_S6(37), BUTTON_S7(38), BUTTON_S8(39), BUTTON_M1(48), BUTTON_M2(49), BUTTON_M3(50), BUTTON_M4(51), BUTTON_M5(52), BUTTON_M6(53), BUTTON_M7(54), BUTTON_M8(55), BUTTON_R1(64), BUTTON_R2(65), BUTTON_R3(66), BUTTON_R4(67), BUTTON_R5(68), BUTTON_R6(69), BUTTON_R7(70), BUTTON_R8(71), BUTTON_PREV_TRACK(58), BUTTON_NEXT_TRACK(59), BUTTON_CYCLE(46), BUTTON_MARKER_SET(60), BUTTON_MARKER_PREV(61), BUTTON_MARKER_NEXT(62), BUTTON_BACKWARD(43), BUTTON_FORWARD(44),BUTTON_STOP(42), BUTTON_START(41), BUTTON_REC(45); private final int id; private KorgNanoControl2(final int id){ this.id = id; } @Override public int getId(){ return id; } }
実際に操作に使用してみると、結構便利で面白いです。しかし、描画するもののパラメータに0から127までの値をうまく対応付ける事が難しいですね。
Common Lispでクライン群の極限集合を描画した
この記事はりひにーさんのLisp Advent Calendar 2014 25日目の記事です。
期間を過ぎていますが余っていたので埋めました。
今までProcessingから始まりジャバ、C++、CUDAで並列演算、とやってきたクライン群の極限集合の描画をCommon Lispでやったので記事にします。
アルゴリズムとか数学的に詳しい話はインドラの真珠という本を読んでみてください。
インドラの真珠|日本評論社
今年はこの本に出会えたのが色々と大きかったです。
何をやっているか
何をやっているかものすごくざっくりと説明すると、
メビウス変換を表す2x2複素数行列、a、bとその逆行列A、Bを様々な組み合わせで用いて無限に変換を行った後の極限点を求めて描画しているという感じでしょうか。
その極限点の集合をどう求めるのかというと、このようなグラフを探索するのです。
各ノードのラベルを有限長なので有限語と呼びます。今回は深さ優先で探索していきます。たどり着いたそれぞれのノードの有限語に無限長の語、aaa...やabABabAB...等の無限語を適用することで極限を求めます。無限語は変換の固定点を用います。例えばaaa...という無限語であれば、aの固定点、abABabAB...という無限語であればabABの固定点を用いる、といった具合です。
あらかじめ決めた深さに達するか、描画するのに十分な深さまで探索されたと判断されたならば、その点を描画し、次の枝に移ります。
実行結果
(defparameter *magnification* 200) (get-limiting-set 2 2 15 0.01) (draw-limiting-set)
(defparameter *magnification* 200) (get-limiting-set #c(1.95 0.02) 3 25 0.01) (draw-limiting-set)
(defparameter *magnification* 8) (get-limiting-set #c(1.91 0.05) #c(1.91 0.05) 20 0.1) (draw-limiting-set)
コードについて
相当酷いコードだと思いますがgistにおいてあります。処理系はclisp-2.49です。
色々な人のlispコードを見てちゃんとしたlispコードの書き方を勉強しなければなりません。
クライン群の極限集合を描画する
注意点ですが、配列のインデックスは1始まりになっています。インドラの真珠に載っていたコードの配列インデックスが1始まりだったからです。
無限大
Common Lispに無限大は規定されていないらしいです。処理系によってはサポートされているらしいのですがCLispにはないみたいです。とりあえずmost-positive-fixnumを無限としておきました。
(defparameter *inf* (* most-positive-fixnum #c(1 1)))
行列クラス
*演算子をオーバーロードできれば良いのですけど、できるかわかりませんでした。mat*という名前で定義しておきましたが気持ち悪い名前だと思います。
print-objectメソッドによってオブジェクトがプリントされる内容を制御できます。
固定点の計算
fix-plusという名前で定義してあります。√の前の符号で二つ固定点が出る時があります。今回は+の方でとってあります。僕が試した範囲では得られる図形に変わりはなかったように思います。
変換の取得
set-gensでは二つのパラメータからメビウス変換の行列を求めています。その中でt_abという変数を求めているのですが、ここも√の前の符号で二つの値がでます。この場合では+をとるか-をとるかで得られる図形が結構変わります。今回は-を取ってあります。
数式の入力
これまで極限集合の描画プログラムを書いてきて結構きつかったのが数式の入力です。複雑で読みにくく、バグの温床です。
lispでは改行を入れることでだいぶ見やすくなると感じました。これはなかなか面白いですね。マクロを使えばべき乗等も含め楽になったりするんですかねぇ。
(setf (z0 (/ (* (- t_ab 2) t_b) (+ (- (* t_b t_ab) (* t_a 2)) (* t_ab #c(0 2))))))
ジャバだとこうでした。めちゃくちゃつらい。
Complex z0 = t_ab.sub(2.0).mult(t_b).div(t_b.mult(t_ab).sub(t_a.mult(2.0)).add(t_ab.mult(new Complex(0, 2.0))));
complex<double> z0 = (t_ab - 2.0) * t_b / (t_b * t_ab - 2.0 * t_a + t_ab * complex<double>(0, 2.0));
末尾再帰の最適化について
極限集合の探索を深さ優先で行う関数を再帰で書いたのですが、ある一定の深さ以上を探索するとデバッガが起動せず、Lisp connection closed unexpectedly: connection broken by remote peerなどといったメッセージとともにSLIMEが落ちてしまいました。きちんとしたエラーメッセージが全く出なかったのですが、Twitterでデバッガに落ちなかったエラーメッセージは*inferior-lisp*バッファに表示されることもあるとのリプライをもらったので確認してみるとスタックオーバーフローのエラーでした。どうやら末尾再帰が最適化されていなかったようです。
末尾再帰の最適化はCommon Lispの規格で決まっているわけではないので処理系に依存しますが、CLispではコンパイル時に最適化されるそうです。compileコマンドで関数のみをコンパイルすることができるようなので、定義後にコンパイルし、最適化してやりました。
こんな感じ
(compile 'explore-limiting-set-with-dfs)
ちなみに最適化する方法がわからなかった時にループで探索を行うコードも書いたのでgistにはコメントアウトして置いてあります。
遅い
遅いです。以前ジャバで組んだものと比べてものすごく遅いです。パラメータによっては全く計算が終わりません。僕の組み方が非効率的なのでしょうけど型や高速化のオプションをつけての高速化でどのくらい速くなるんでしょうか。これは今後の課題ですね。
おわり
Lisperへの道は長く険しい。
Common Lisp、cl-openglでマンデルブロ集合を描画した
この記事はりひにーさんのLisp Advent Calendar24日目の記事です。
昨日はりひにーさんの本当はコワイ副作用の恐怖 - 想像力の欠如は深刻な欠点の一つである。でした。
以前(ほとんど1年前ですが)common lispでマンデルブロ集合を計算させました。前は計算結果を書き出してProcessingで描画しましたが、OpenGLを使えるようになったのでcl-opengl(cl-glut)を使ってlispで描画したいと思います。
OSはwindows8.1、処理系はclisp-2.49、エディタはEmacsのSLIME 2.5(たぶん)です。
コードはこちら。
cl-glutによるマンデルブロ集合の描画
下準備
freeglut.dllを用意する。
OpenGLで描画したものをウィンドウなどに表示させるためにfreeglutを使います。そのfreeglutをCommon Lispで扱うcl-glutを使用するためにはfreeglut.dllが必要らしく、これは自分でソースコードからコンパイルして入手する必要があります。ちょっと面倒ですね。検索すればたくさん関連の記事が見つかるので頑張ってください。
コードを書く
コード
OpenGL絡みのところのみを記載します。
(defclass my-window (glut:window) () (:default-initargs :title "mandelbrot" :width *width* :height *height* :mode '(:single :rgb :depth))) (defmethod glut:display-window :before ((w my-window)) (gl:clear-color 1 1 1 0) (gl:matrix-mode :projection) (gl:load-identity) (gl:ortho 0 *width* *height* 0 -1 1)) (defun set-mandelbrot-vertexes (latice-points) (mapcar #'(lambda (x) (let ((latice-point (car x)) (n (cadr x))) (cond ((= n -1) (%gl:color-3f 0 0 0)) (t (%gl:color-3f (* n 0.2) 0 0))) (gl:vertex (* *magnification* (realpart latice-point)) (* *magnification* (imagpart latice-point)) 0))) latice-points)) (defmethod glut:display ((window my-window)) (gl:clear :color-buffer-bit) (%gl:color-3f 0 0 0) (gl:push-matrix) (gl:translate (/ *width* 2) (/ *height* 2) 0) (gl:begin :points) (set-mandelbrot-vertexes *mandelbrot*) (gl:end) (gl:pop-matrix) (gl:flush)) (defparameter *mandelbrot* (get-mandelbrot)) (defun draw-mandelbrot () (glut:display-window (make-instance 'my-window)))
OpenGL、freeglutを触ったことがあればメソッド名はそのままなのであまり問題はないと思います。ウィンドウのクラスを作った後はdisplay、keyboard、reshapeなどのメソッドは書いて評価するだけでよく、登録は必要ないみたいです。スッキリして良いです。
気になったのは%gl:color-3fの部分で、ライブラリの指定に%をつける記法を見たことがありませんでした。ちゃんと調べていないのでまだ何なのかはわかっていません。
注意すべきところは正射影の設定を行うgl:orthoの部分です。サンプルからそのまま持ってきていたりすると画面の両端が-1から1までになってしまったりします。
実行結果
EmacsでProcessingを実行する際につまずいたこと
processing-mode自体はすんなり入ったんだけどemacsからの実行で詰まった。
4時間くらい格闘してemacsLispのコードを解読してみたりとか、結果的にいい経験になったのかな
環境はwindows7 64bit、gnupack11.0basic、Processing2.1.1
導入はProcessing を Emacs で書いて Emacs から実行するを参考にさせていただきました。
ソースコードはgitを使わなくてもhttps://github.com/ptrv/processing2-emacsからzipでダウンロードできる。*1適当な場所に解凍した後は設定ファイルに以下のように記述するのだけれど、ここで注意が必要なのが~/.emacs等がすでにあるとそちらが呼ばれて~/.emacs.d/init.elは読み込まれないということ。
今回の僕の環境、gnupack11.0basicに入っているemacsはあらかじめ色々と設定が施されており、その設定ファイルは~/.emacs.d/init.elにある。これに気付かずに~/.emacsを作成して追加の設定を記述してしまった僕のemacsは、shellが正しく起動されず、processing-javaコマンドが打てなかったらしい。
初心者はやりがちなのかもしれない。
(add-to-list 'load-path "~/elisp/processing2-emacs/") (autoload 'processing-mode "processing-mode" "Processing mode" t) (add-to-list 'auto-mode-alist '("\\.pde$" . processing-mode)) (setq processing-location "c:/processing-2.1.1/processing-java.exe") (setq processing-application-dir "c:/processing-2.1.1/processing.exe") (setq processing-sketch-dir "c:/Users/soma/Documents/Processing")
次に適当なProcessingコードを書いて実行させるのだけれど、今度はコマンドをつかうとスケッチと出力ファイルのパスが以下のようになってしまう。
f\:/workspace/program/processing/ProcessingTest/
つまりf:/の部分に\が挿入されてパスがおかしくなってしまう。
これはprocessing-mode.elのprocessing-make-compile-commandの定義に使われているshell-quote-argument関数が原因らしい。shell-quote-argument関数はマニュアルによるとシェル構文で表した引数を文字列で返す関数らしい。これによってc:/部分がおかしくなってしまったみたいだ。
UNIXとwindowsの違いで駄目な感じなんだと思う。
僕はとりあえずprocessing-mode.el中の三箇所(147、157、159行目)のshell-quote-argumentをすべてevalに直すことで動作した。
これで快適なProcessing環境が整いました。
めでたしめでたし
Common Lispでマンデルブロ集合を計算してみた
Common Lispで複素数が扱えたので計算してProcessingで描画してみた。
1か月前くらいに下書きを書き始めたのだけれど、改めて記事にしてコードを眺めると粗が目立ったりでどうにも…
コードはここ
https://gist.github.com/soma-arc/9564293
マンデルブロ集合についてはマンデルブロ集合の不思議な世界さんがとても詳しく、わかりやすいです。初めて書いた時にとても参考になりました。
簡単に計算の仕方をまとめると、複素平面上に格子点を取ってその座標を複素数Cとし、以下の漸化式を繰り返し計算する。
が無限大に発散しないものがマンデルブロ集合となる。
また、となると無限大に発散することがわかっている。
今回のマンデルブロ集合の描画は、漸化式の計算回数で色分けを行っている。
(defparameter *width* 500) (defparameter *height* 500) (defparameter *reduction* 100.0) (defun get-latice-points (width height reduction) (apply #'append (loop for x from (* -1 (/ width 2)) below (1+ (/ width 2)) collect (loop for y from (* -1 (/ height 2)) below (1+ (/ height 2)) collect (complex (/ x reduction) (/ y reduction))))))
第一引数の関数にリストの要素すべてをその関数の引数として渡すapplyとリストの結合を行うappendを使って格子点のリストを得る。
格子点の間隔は1だと広すぎるので*reduction*で割って0.01刻みにしている。
また、0.01などの少数は、二進数にすると無限小数になってしまう場合があり、計算に誤差が出るので注意。0.01を直接かけると誤差が出るので100で割った
(defun calc-mandelbrot (c) (labels ((f (z c n) (cond ((= n 27) `(,c nil)) ((< 2 (abs z)) `(,c ,n)) (t (f (+ c (expt z 2)) c (1+ n)))))) (f 0 c 0)))
漸化式の計算部分。
27回計算しても発散しないとわかったら計算を打ち切ってマンデルブロ集合とする。
途中でzの絶対値が2より大きくなれば発散するものとして計算回数をくっつけてCと一緒に返す。
(defun get-mandelbrot () (mapcar #'calc-mandelbrot (get-latice-points *width* *height* *reduction*))) (defun to-csv (coordinates-count) (let ((coordinates (car coordinates-count)) (recursion-count (cadr coordinates-count))) (concatenate 'string (write-to-string (realpart coordinates) ) "\," (write-to-string (imagpart coordinates) ) "\," (write-to-string recursion-count)))) (defun to-csv-file (calculated-latice-points) (with-open-file (*standard-output* "mandelbrot.csv" :direction :output :if-exists :supersede) (mapcan (lambda (s) (format t (concatenate 'string s "~%"))) (mapcar #'to-csv calculated-latice-points))))
残りの部分は格子点リストに対して計算を行う関数、座標と結果のリストをcsvにする関数、そしてそれらをファイルにする関数。
with-open-file中ではprincで出力するとうまく改行が行われなかったので、formatを使い、改行文字と結合してみた。メモ帳で開くと見た目は改行されていないが問題ない。
それとcuspを使ってファイル出力を行う時は絶対パスで指定する必要があるみたいだ。おそらくcuspのインタープリタの場所がカレントディレクトリになっているのだと思う。
(to-csv-file (get-mandelbrot))
あとは手に入ったcsvをごにょごにょして描画しよう。
Processingではこんな感じ。
void setup(){ size(500, 500); translate(250, 250); String lines[] = loadStrings("mandelbrot.csv"); for(String line : lines){ String tmp[] = line.split(","); float x = (float(tmp[0])); float y = (float(tmp[1])); int count = 0; if(!tmp[2].equals("NIL")){ count = int(tmp[2]); } stroke(0, count * 255 / 12, count * 255 / 12); point(round(x * 100), round(y * 100)); } }
縮小してあった100をかけてプロットする。しかし、ここでも誤差が出るらしく、roundで丸める必要がある。
今回は緑と青を混ぜた感じで描画してみた。
今のツイッターアイコンです
黒い部分がマンデルブロ集合。端の部分をうまく拡大できれば自己相似性が見える感じなのだけれど色々面倒くさそうなので僕はまだ試してません。
とりあえず自分なりに一つかけたので次はどうしようか。
Newmanアルゴリズムを一度組んであるのでlispでも組んでみようと思ってはいる。
GNU Emacs導入
Windows版GNU Emacsを導入した。ここのページ最下部からおそらく最新版だと思われるemacs-24.3-bin-i386.zipをダウンロード、解凍。すぐ使えた。
ただ、このままだと日本語入力に難があって、Enterで確定するまでエディタに表示されず、変換候補も右下にでてしまう。解消するには自分でパッチをあてるか、パッチが既にあてられたEmacsが入ったgnupackなるものをダウンロードすればいいらしい。そういうのは面倒なのでとりあえず後回しにする。
使ってみたところ、かなり癖のあるキーバインドだと感じた。使いこなせば魔法のような速度らしいが僕にはさっぱり想像できない。とりあえず入門GNU Emacsを読みつつ触っていこうと思う。
ところで、入門GNU EmacsによればEmacsは世界で最も使われているエディタの一つらしい。けれどもTwitterや技術系ブログでEmacsの話をしている人を全く見たことがない。僕の観測範囲が狭いだけなのかもしれないけれどVimの話は黙ってても目に入るし、人口的にはどうなってるんだろうね。
Land of Lisp読了
Land of Lisp読み終わった。
Dice of Doomのweb版、FireFoxでアクセスしてみたところ、HTMLが直接表示されて動かなかった。原因はわからないけれどもIEでアクセスしたところ正しく表示された。html5の設定もtrueにしてあるのになぜだったんだろうか。
内容
愉快なイラストにLisp万歳な文章が楽しかった。Lispコードのエレガントさもだいたいわかったよ、タブン。特に興味を魅かれたのは遅延評価の部分。無限リストなんて何かロマンがあるじゃないか。正直αβ法あたりのコードは難解でさっぱりだったのだけれども、とりあえず考え方はわかったので良しとする。一つ気になったのが索引があんまり役に立たなかったこと。いくつかの関数の仕様を忘れてしまい、索引から参照してもわからなくて何回かググった。