ISLispにて竹内関数音楽


実演の様子(Youtube)

はじめに

EISL ISLispのコンパイラ(FASTといいます)にはGCCのソースコードをそのままISLisp関数に埋め込む機能を取り入れています。GCCの豊富なライブラリを煩雑なFFI、CFFIを介することなく、簡便に利用することことを目的としています。手始めにWindowsのAPIを呼び出してMIDI音源を操作することを題材にテストをしました。

竹内関数とは

計算機科学者、Lisperである竹内郁雄先生が考案した関数です。次のように定義されています。

wikipediaより引用
https://ja.wikipedia.org/wiki/%E7%AB%B9%E5%86%85%E9%96%A2%E6%95%B0

普通にLispで書くと次のようになります。

(defun tarai(x y z)
  (if (<= x y)
      y
      (tarai (tarai (- x 1) y z)
             (tarai (- y 1) z x)
             (tarai (- z 1) x y))))

大量の再帰計算を要するのが特徴です。これを利用してLispなど再帰型の処理系の性能テストに使われています。また、数学的にもとても興味深い関数です。

aikeさんが、この竹内関数の利用して音楽を生成するということをされました。不思議なメロディーが生成されます。ご本家の竹内先生もご興味をもったようです。aikeさんのブログに竹内先生、ご自身がコメントをなさっています。
http://d.hatena.ne.jp/aike/20111112

竹内関数を鳴らす

midiの機能を利用した竹内関数音楽バージョンは下記の通りです。

(defun tarai (x y z)
  (note-on (+ (* 2 x) 72))
  (sleep 100)
  (note-on (+ (* 2 y) 72))
  (sleep 100)
  (note-on (+ (* 2 z) 72))
  (sleep 100)
  (if (<= x y)
      y
      (tarai (tarai (- x 1) y z)
             (tarai (- y 1) z x)
             (tarai (- z 1) x y))))


MIDI音階は半音なので、2倍にして全音での移動としています。72というのは12×6でありオクターブを調整しています。

コンパイラ拡張機能

FASTコンパイラには次の関数が追加されています。

(c-include str) 文字列strを #inlude 文としてCソースに埋め込みます。
(c-define str) 文字列strを #defin文としてCソースに埋め込みます。
(c-lang str) 文字列strを Cソースコードとして埋め込みます。
(c-option str) GCCでコンパイルするときにstrをコンパイラオプションに与えます。

コンパイル

FASTコンパイラを使って巻末のソースコードをコンパイルしてください。midi.lspとファイルに記録し(compile-file "midi.lsp") によりmidi.oというオブジェクトファイルが生成されます。これを(load "midi.o")とすることによりコンパイル後のものがロードされます。

使い方

ロードしたら、まずmidi回線をオープンします。 (open-midi)とします。
次に(tarai 4 2 0)のようにして起動します。引数が大きいと当分終了しませんのでご注意ください。音色を変えるには(voice 100)のようにして音色を与えてください。デフォルトはピアノです。
終わったら(close-midi)でMIDI回線がクローズされます。

FASTコンパイラ

コンパイラの詳細につきましては下記の記事をご参照ください。
http://qiita.com/sym_num/items/793adfe118514668e5b0

まだ開発途上にありますが、ver0.65よりこのコードがコンパイル可能なことを確認しています。

ソースコード

小整数は即値になっています。0あるいは正数の場合には上位第二ビットを立てて、即値と識別しています。負数の場合にはセルの番地の範囲外でありそのまま即値と識別されます。これをC言語の中で利用する場合には INT_MASKとのandをとってください。 INT_MASK & x のようにして使います。ISLispの変数はすべて大文字に変換されてCソースになっていますので、大文字で記述してください。

(c-include "<windows.h>")
(c-include "<mmsystem.h>")
(c-option "-lwinmm")
(c-define "MIDIMSG(status,channel,data1,data2)"
          "( (DWORD)((status<<4) | channel | (data1<<8) | (data2<<16)) )")
(c-lang "HMIDIOUT hMidiOut;")

(defun midi-out-open ()
  (c-lang "midiOutOpen(&hMidiOut, MIDI_MAPPER, 0, 0, CALLBACK_NULL);" )
  t)

(defun midi-out-close ()
  (c-lang "midiOutClose(hMidiOut);")
  t)

(defun midi-out-short-msg (st ch d1 d2)
  (if (not (integerp st)) (error "midi-out-short-msg" st))
  (if (not (integerp ch)) (error "midi-out-short-msg" ch))
  (if (not (integerp d1)) (error "midi-out-short-msg" d1))
  (if (not (integerp d2)) (error "midi-out-short-msg" d2))

  (c-lang "midiOutShortMsg(hMidiOut,MIDIMSG((BYTE)(INT_MASK & ST),(BYTE)(INT_MASK & CH),(BYTE)(INT_MASK & D1),(BYTE)(INT_MASK & D2)));")
  t)

(defun open-midi ()
  (midi-out-open))

(defun close-midi ()
  (midi-out-close))

(defglobal volume 127)

(defglobal tempo 240)

(defglobal channel 0)

(defun note-on (n)
  (midi-out-short-msg #x9 channel n volume))

(defun note-off (n)
  (midi-out-short-msg #x8 channel n volume))

(defun all-note-off ()
  (midi-out-short-msg #xb channel #x7b 0))

(defun all-sound-off ()
  (midi-out-short-msg #xb channel #x78 0))

(defun reset-all-controller ()
  (midi-out-short-msg #xb channel #x79 0))

(defun voice (n)
  (midi-out-short-msg #xc channel n 0))

(defun sleep (n)
  (for ((i (* n 1000000) (- i 1)))
       ((<= i 0) t)))


(defun tarai (x y z)
  (note-on (+ (* 2 x) 72))
  (sleep 100)
  (note-on (+ (* 2 y) 72))
  (sleep 100)
  (note-on (+ (* 2 z) 72))
  (sleep 100)
  (if (<= x y)
      y
      (tarai (tarai (- x 1) y z)
             (tarai (- y 1) z x)
             (tarai (- z 1) x y))))



展望

このように簡単にGCCの機能を取り込むことができます。ほかにもラズパイのWiringPIライブラリですとか、高速グラフィクスもGCC経由で呼びだせるでしょう。多様な使い道があると思います。ぜひ、ご活用ください。