REAPERにはJSFXという、スクリプトを書くだけでVSTのようなオリジナルのプラグインエフェクトが作成できる機能があります。この使い方に関する情報はほとんど公式ドキュメントだけなんですけど、ちょっと調べてみました。
VSTのような、と書きましたがいきなり音声信号処理をやるよりもまず視覚的にわかりやすいところから手を付けよう、ということでまずは画面描画系の処理から。音声信号処理は今度調べます。
Hello World
まずはお約束のHello Worldです。
desc:my first effect
@init
hello = "Hello World";
@gfx
gfx_r = 0.2;
gfx_g = 0.8;
gfx_b = 0.6;
gfx_measurestr(hello, strw, strh);
gfx_x = gfx_w / 2 - strw / 2;
gfx_y = gfx_h / 2 - strh / 2;
gfx_drawstr(hello);
Hello Worldの文字がウィンドウ中央に表示されれば成功です。
説明
最初の行desc:my first effect
はエフェクトの名前を指定するものです。desc:
以降は何でも構いません。
@init
以下のブロックは初期化処理を行う部分です。エフェクトを読み込んだ時やコードを編集して再コンパイルされた時等に呼び出されます。
このサンプルではhello
という変数に文字列を代入しています。変数は事前に宣言する必要はありません。
画面に描画する処理は@gfx
以降に書きます。このコードブロックは1秒間に約30回実行され、この中でのみgfx_
で始まる関数や変数を使用することができます。このブロック外でgfx_
で始まる関数や変数を使用すると、正しく動作しなかったり不明な動作をすることがあるので注意してください。
gfx_r
,gfx_g
,gfx_b
は見ての通り色のR,G,Bを表す変数です。この値は後続するgfx_
の描画系関数で描画される図形の色を指定します。
それぞれの値は0.0~1.0の間で指定します。
gfx_drawstr()
は文字列を描画する関数です。描画位置はgfx_x
,gfx_y
で事前に指定します。
画面の幅、高さはそれぞれgfx_w
,gfx_h
で取得できます。文字列の幅、高さはgfx_measurestr(文字列, 幅を格納する変数, 高さを格納する変数)
で取得できます。
今回は画面の中央に表示したかったので、画面中央のX,Y座標を計算しました。画面の中心計算方法は(画面幅/2, 画面高さ/2)
で求められますが、これだと画面の中央が文字列の左端になってしまうので、そこからさらに文字列の長さの半分だけ左に、文字列の高さの半分だけ上に移動します。
マウスの位置
マウスの位置を取得するにはmouse_x
,mouse_y
変数を使用します。
desc:my mouse effect
@gfx
gfx_r = gfx_g = gfx_b = 1; // white
hello = "<- This is mouse";
gfx_x = mouse_x;
gfx_y = mouse_y;
gfx_drawstr(hello);
マウスカーソルの位置に白い文字が描画されれば成功です。
mouse_x
,mouse_y
変数はそれぞれウィンドウ内の描画領域の左上からの相対座標を表します。X座標は右が正、Y座標は下が正です。
今回はこの値をそのままgfx_x
,gfx_y
に入れています。gfx_drawstr()
はこの位置から描画を開始するのでした。
ランダムな値
ランダムな値を取り入れると勝手に動く面白いモノが作れそうです。
desc:random color
@gfx
gfx_r = rand(1);
gfx_g = rand(1);
gfx_b = rand(1);
hello = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
gfx_x = 10;
gfx_y = 10;
gfx_drawstr(hello);
rand()
関数は0以上引数で指定した値未満のランダムな数を返します。引数を省略した場合は1
を与えたのと同じことになります。(0.0~1.0の間の数。ただし1.0は含まない)
ちなみに、この例では文字列がとても長いので、ウィンドウが小さいと描画領域からはみ出てしまいます。自動で改行などはしてくれないので注意しましょう。
時間を取得
リアルタイムの信号処理において時間という値はとても大事です。
desc:show time
@gfx
gfx_r = rand();
gfx_g = rand();
gfx_b = rand();
gfx_x = 10;
gfx_y = 10;
t = time_precise();
gfx_printf("%f", t);
現在の時刻を取得する関数は2種類あります。time()
とtime_precise()
ですが、前者は1970年1月1日からの経過秒数を整数で返し、後者はシステム依存のタイムスタンプを小数で返します。
絶対的な日時や日付などが重要な場合はtime()
を、ある時点からの相対的な経過時間などが必要な場合はtime_precise()
を使うと良いでしょう。
ちなみにここで初登場したgfx_printf()
という関数ですが、C言語のprintf
のような感じで画面に文字列を描画します。gfx_drawstr()
では数値の変数を描画することができません。数値を画面に表示したい場合はgfx_printf("%f", 数値変数)
のように書きます。
条件分岐
desc:condition
@gfx
gfx_r = rand();
gfx_g = rand();
gfx_b = rand();
gfx_x = 10;
gfx_y > gfx_h ? gfx_y = 0 : gfx_y += 10;
gfx_drawstr("AAAAAAAAAA");
なんと、**JSFXではif
文が使えません!**その代わりに条件演算子を使って表します。
この例ではgfx_y
が描画領域の高さよりも大きくなったらgfx_y
を0に設定し、そうでなければgfx_y
の値に10を加算する、という処理をしています。これがgfx_y > gfx_h ? gfx_y = 0 : gfx_y += 10;
の部分で行われている処理です。
if文で書いたとすると
if (gfx_y > gfx_h) {
gfx_y = 0;
} else {
gfx_y += 10;
}
というような感じになります。
もし◯◯だったら、の中のコードが複数になる場合は()
でくくって複数のコードを書くこともできます。
条件 ? (
...
...
) : (
...
...
);
という感じで。
繰り返し
desc:condition
@gfx
gfx_r = rand();
gfx_g = rand();
gfx_b = rand();
gfx_x = 10;
gfx_y = 10;
loop(5, gfx_drawstr("Hello"));
gfx_x = 10;
gfx_y = 30;
while(gfx_x < gfx_w) (
gfx_drawstr("He");
);
一定回数の繰り返し処理を行うにはloop(回数, 処理)
のように書きます。回数には変数を与えることも可能ですが、loop
に入った時点の値でループ回数が決まり、ループ内で変数を変更しても反映されません。
他のプログラミングに慣れている人はwhile(条件) (処理)
という書き方の方がしっくり来るかもしれません。上記の例ではgfx_x < gfx_w
の条件を満たす間、gfx_drawstr("He")
の処理を繰り返します。
上記の例でgfx_x
の値をいじっている箇所はどこにもないのに、なんで無限ループにならないんだろう?と思った方もいるかもしれません。
実はgfx_drawstr
は内部で**描画した文字列の分だけgfx_x
,gfx_y
の値を増やします。**なので、gfx_drawstr
を繰り返し呼ぶだけで文字が横に並ぶのです。文字列に改行\n
を含んでいたら、ちゃんとgfx_y
の値も増えていることがわかります。