Lua

PeloReaper Extensionの情報やダウンロードはこちらからどうぞ。
PeloReaper Extension for REAPER is here.
ReaperBanner01_w250 AudioSoftBanner01_w250

ReaScriptのLuaでDLLモジュールを使う

今回はReaScriptのluaでDLLモジュールをrequireする方法を試してみました。
DLLの作り方やluaからの呼び出しについてはググればすぐ出てくるので細かく説明しませんが、手順としては以下のような感じです。

1. REAPERのLuaが5.3系なので、Lua5.3.xのLibを落としてくる(staticリンク系がラク)
2. CでLuaに関数を公開するモジュールDLLの作成
3. REAPERの実行ファイル(reaper.exe)がある場所にモジュールDLLを置く
4. ReaScriptのLuaでそのモジュールをrequireして公開された関数を呼ぶ

LuaモジュールDLLの作成

まずはLua5.3.x系のLuaをダウンロードしましょう。(今回は5.3.4を使用しました)
ライブラリがバイナリでも配布されているのでそちらを利用するのがラクです。
static, dynamicいずれでも構いませんが、static版がラク。dynamicの場合はlua53.dllをreaper.exeと同じ場所に置く必要があるので注意。

以下のようなプログラムでDLLを作成すればOKです。
※念のため注意ですが、REAPERが64bitなら64bit版のDLLを作成して下さい。
#include "lua.hpp"

extern "C" {

// exportする関数
static int MyCalc_Impl(lua_State *L)
{
	// 最初のスタックから値を読みだす
	double d = lua_tonumber( L, 1 );

	// 戻り値をスタックに積む
	lua_pushnumber( L, d / 2.0 );
	lua_pushnumber( L, d * 2.0 );

	return 2; // 戻り値の個数
}

// テーブル登録用
static luaL_Reg moduleFuncs[] =
{
	{ "MyCalc", MyCalc_Impl },
	{ nullptr, nullptr } // end of table
};
static const int numOfFuncs = (sizeof(moduleFuncs) / sizeof(moduleFuncs[0])) - 1;

// EntryPoint
__declspec(dllexport) int luaopen_MyLuaModule(lua_State *L)
{
	// 以下luaL_register()の替わり
	lua_newtable( L );
	luaL_setfuncs( L, moduleFuncs, 0 );
	lua_pushvalue( L, -1 );
	lua_setglobal( L, "MyLuaModule" );

	return 1;
}

} // extern "C"

DLLが作成できたら、REAPER本体(reaper.exe)と同じフォルダにDLLをコピーします。 (大抵は C:\Program Files\REAPER (x64) など)

ReaScriptでモジュールを使用

作成したモジュールを呼び出すluaスクリプトを作成します。
require("MyLuaModule")
local num1, num2 = MyLuaModule.MyCalc( 1.5 )
reaper.ShowConsoleMsg( "Result:" .. num1 .. "," .. num2 .. "\n" )

実行結果はこちら
LuaRequire_Result01

ということで、Cで作成したDLLをLuaで直接読み込んで実行することが出来ました。

REAPERの拡張ならExtensionを作る方法がありますが、ちょっとしたスクリプトの補助であれば、今回の方法も使える局面がありそうな気がします。
自分はlua初心者なので上記モジュールのソースやLuaサンプルもかなりテキトーですが、luaに精通している人であれば、何かもっといろいろ出来るのかもしれません。自分ももう少し勉強したいところですね。

C++ REAPER Extension (6) : CやReaScript(lua,EEL)にAPIを公開

こんにちは、ぺろりです。
今回はExtensionからC/C++とReaScript(lua,EEL)に対してAPIを公開する方法を紹介します。
Pythonでスクリプトを書く方法は試してないので機会があれば・・・。

ここまで読み進めて来たという方であればコード見ればすぐ分かりそうですので、そこからいきましょう。
/// REAPER経由での公開関数
/// @param[in] num 任意の数値
/// @return てきとーに計算した結果
static int UnkoFunc(int num)
{
	return 2 * num; // てきとーに値を倍にして返すだけ
}
/// 単なる便利用typedef
typedef int (*FUNC_UnkoFunc)(int);

/// Luaから呼び出せるようにするための関数
/// - Script系の言語からはCの関数を直接ではなくて、こういった可変引数的な呼ばれ方をする模様
/// @param[in] arglist luaとかからくる引数リスト
/// @param[in] numParams arglistの要素数
/// @return 結果の値(ここだとUnkoFunc()の戻り値)
static void* __vararg_UnkoFunc( void** arglist, int numParams )
{
	// Cで公開していた関数を呼ぶ
	int res = UnkoFunc( (intptr_t)arglist[0] );
	// 戻り値をvoid*にして返してやる
	return (void*)(intptr_t)(res);
}

REAPER_PLUGIN_DLL_EXPORT int 
REAPER_PLUGIN_ENTRYPOINT(
	REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
{
	// (中略)
	// 初期化時の処理で、以下の登録処理を行う

	// 公開する処理を書く関数を登録
	plugin_register( "API_UnkoFunc", (void*)UnkoFunc );
	// 関数の引数やHelp文字列などの情報を登録
	plugin_register( "APIdef_UnkoFunc",
			"int\0int\0num\0[Pelori:API]Exported Unko API" );
	// luaなどのスクリプト側から呼ばれる関数を登録
	plugin_register( "APIvararg_UnkoFunc", __vararg_UnkoFunc );


	// Cで呼び出せるかのテスト
	// 関数のアドレスをREAPERから取得
	FUNC_UnkoFunc unkofunc_ptr = (FUNC_UnkoFunc)(rec->GetFunc("UnkoFunc"));
	// 呼び出してみる
	int unkoResult = unkofunc_ptr( 3 ); // 3*2=6が返ってくればOK

// (後略)
}

plugin_register( "API_UnkoFunc", (void*)UnkoFunc ) は、"UnkoFunc"という名前でExtensionAPIを登録しています。"API_"以下の部分が公開APIの名前となりますが、登録する関数(第2引数のやつ)の名前は合わせなくても問題ないようです(__UnkoFuncという関数を登録しても問題なかった)。

plugin_register( "APIdef_UnkoFunc", ... ) ではAPIの戻り値、Helpテキストといった情報を登録します。これも登録が必須です。文字列のフォーマットは
"戻り値\0引数の型リスト\0引数の名前リスト\0Helpテキスト"
といった感じでしょうか。複数引数があるなら、例えば "int\0int,double\0arg1,arg2\0Hoge Function" という感じです。

plugin_register( "APIvararg_UnkoFunc", ... ) はスクリプトから呼ばれる関数登録となります。スクリプトからは "API_○○" で登録した関数が直接呼ばれるわけではありません。"APIvararg_○○" で登録した関数が呼ばれることになります。
スクリプトから呼び出されると、引数(arglist)がどれだけ積まれたか(numParams)という情報がやってきますので、必要なだけ情報を引用して結果を返してやればいいようです。今回のサンプルだと UnkoFunc を呼んで結果を返してやればOK。

あとはExtension終了時に plugin_register( "-API_UnkoFunc", (void*)UnkoFunc ); などで登録したものを解除して実装完了です。(このへんはまぁ、もう分かりますよね)

実際のところ、1つのAPI登録で3つの登録用文字列が必要なことから、沢山APIを公開しようとすると結構煩雑になっていくと思います。SWSのReaScript.cppあたりを見てみると、マクロで文字列を一括定義したりといった部分などもろもろ参考になるのではないかと思います。



これらを実装したExtensionをREAPERにインストールしてからREAPERを起動して、reaper_plugin_functions.hを書き出す(Action"[developer] Write C++ API functions header"で[Write functions exported by 3rd party extensions]にチェックを入れる)と、自分で登録した関数(ここだとUnkoFunc)がヘッダに出力されて使えるようになります。
// (↓ reaper_plugin_functions.hに書き出された部分)
#if defined(REAPERAPI_WANT_UnkoFunc) || !defined(REAPERAPI_MINIMAL)
REAPERAPI_DEF //==============================================
// UnkoFunc
// [Pelori:API]Exported Unko API

  int (*UnkoFunc)(int num);
#endif

・・・っと書いたものの、自分の環境だとこの3rdPartyの関数も出力したreaper_plugin_functions.hを使おうとしてみたところ、他のExtensionが公開している関数で使われている構造体などの型が見つからず、コンパイルエラーになってしまいました(このへんもうちょっと頑張って取り組むのは後にしようかと思います)。
なので、自分は前述のサンプルコードにあるようにGetFunc()で関数ポインタを取得して使う方法を今のところ使っています。

ひとまずC++側では呼べるようになったので、次はluaで呼び出してみます(ActionListでReaScriptのNewボタンでluaファイルを作成してスクリプトを書きます)。
val = reaper.UnkoFunc(3)
reaper.ShowConsoleMsg(val)
QS_20180128-190450
ハイ、簡単ですね。

ついでにEELでも試してみます。今度は.eelファイルを作成して書きます。
EELではExtension APIの呼び出しに extension_api() を使います。
num = extension_api("UnkoFunc", 3);
sprintf(#str, "Result:%d", num);
ShowConsoleMsg(#str);
QS_20180128-192654

ちなみにExtensionからの公開関数はREAPERのメニューから、[Help > ReaScript documentation]でHTML出力されるので、自分の関数がこのHTMLにも表示されるようになります。
QS_20180128-195149


今回はREAPERのExtension API公開方法を解説しました。
これでスクリプトからもあなたの関数を呼び出すことができて、また一段とREAPER機能実装の自由度が上がるのではないかと思います。

しかしこのDAW、ホントやりたい放題出来てオモチャとして面白すぎる・・・

ではまた次の記事でお会いしましょう。
このブログについて
ぺろりがREAPERで遊びたいというだけのブログかもしれない

必ずこちらをお読みください

twitter: @pelori

管理人用
  • ライブドアブログ