2018/01

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

C++ REAPER Extension (3) : ExtensionMenu追加

今日は雪が降ってかなり寒いのですが、こんばんはぺろりです。

今回はREAPERのメインメニューにあるExtensionメニューの実装について書いていきます。

まずはDLLのエントリポイントで以下のようなコールバック登録とExtensionMenu作成を行います。
// Menuに反応するコールバック
void HookCustomActionMenu(const char* menustr, HMENU hMenu, int flag);
// Actionの有効・無効の問い合わせでMenu意外でもあちこちから呼ばれるコールバック
int HookToggleAction( int actionID, int flag );
// REAPER_PLUGIN_ENTRYPOINT()でのコールバック登録 plugin_register( "toggleaction", (void*)HookToggleAction ); plugin_register( "hookcustommenu", (void*)HookCustomActionMenu );

// ExtensionMenuを作成
// (SWSなど他のExtensionが先に行っているという場合もあるがかぶっても問題なし)
AddExtensionsMainMenu();
これでMainMenuの仕組みからコールバックが呼ばれるようになります。

また、呼ばれるタイミングはメニュー生成時とメニュー呼び出し時の2種類があります。
呼び出し元はExtensionメニューだけでなく、あらゆるMainメニュー項目から呼び出されるのですが、どこから呼ばれたかは上の例だと HookCustomActionMenu() の引数 menustr を見ると分かります。
(MainメニューのExtensionsをクリックすると"Main extensions"という文字列がやってきます)

■Menu生成時の処理
例えば、MainメニューのExtensionsを初めてクリックした時、Extensions以下のメニュー項目の作成を行うため、コールバック(HookCustomActionMenu)が呼ばれます。
この時、引数の flag=0 になっていますので、以下のような実装でメニュー項目を追加します。
// Extensionメニューにセパレータ、サブメニュー、メニュー項目を追加してみるテスト
if ( 0 == flag )
{
	if ( 0 == strcmp( menustr, "Main extensions" ) )
	{
		MENUITEMINFO mi = { sizeof(MENUITEMINFO), };
		// まずセパレータ追加
		mi.fType = MFT_SEPARATOR;
		mi.fMask = MIIM_TYPE;
		InsertMenuItem( hMenu, GetMenuItemCount( hMenu ), true, &mi );
		// サブメニューを追加
		HMENU hSubMenu = CreatePopupMenu();
		mi.fMask    = MIIM_SUBMENU | MIIM_TYPE | MIIM_STATE;
		mi.fState   = MFS_UNCHECKED;
		mi.fType    = MFT_STRING;
		mi.hSubMenu = hSubMenu;
		mi.dwTypeData = L"Pelori";
		InsertMenuItem( hMenu, GetMenuItemCount( hMenu ), true, &mi );
		// メニュー項目追加
		const int cmdID = 0; // ここは自分で事前に登録したActionIDをセット
		mi.fMask  = MIIM_TYPE | MIIM_ID | MIIM_STATE;
		mi.fType  = MFT_STRING;
		mi.dwTypeData = L"Test Action"; // Menuに表示されるテキスト
		mi.fState = MFS_UNCHECKED;
		mi.wID    = cmdID; // このメニュー項目で実行するActionIDを指定
		InsertMenuItem( hSubMenu, GetMenuItemCount( hSubMenu ), true, &mi );
	}
}
これでメニュー項目の登録が完了。次はこれを使う時の処理です。
(ちなみにこれはWindows版の場合です。何をやっているか意味不明な方はWin32APIのメニューまわりを調べてください。Mac版とかの方はよく知りませんごめんなさい)


■Menu呼び出し時の処理
Extensionsメニューなどをクリックすると、今度は flag=1 になって HookCustomActionMenu() が呼ばれますが、今のところはここで特に何もする必要はありません。
そして HookCustomActionMenu() の次に HookToggleAction() コールバックが呼び出されます。
これはメニュー項目のチェック表示をON/OFFするために、ActionIDに対する有効・無効状態をREAPERが問い合わせてくるというものです。
int HookToggleAction( int actionID, int flag )
{
	// 戻り値の仕様
	// -1: 自分のExtensionと関係ない、もしくはON/OFFといったトグルを行わない
	//  0: actionIDに対応するアクションは無効状態です
	//  1: actionIDに対応するアクションは有効状態です
	return -1;
}
今回の例では上のコードのように -1 を返してスルーで問題ありません。
ここで 1 を返すと、以下の画像のようにチェックが付きます。
QS_20180122-233248

そして、今回メニューに追加した"Test Action"をクリックすると、ここに登録したActionIDのアクションが実行されます。具体的には以下のコールバックが呼び出されます。
・plugin_register()に "hookcommand2" で登録したコールバック

まだ特に触れていませんでしたが、↑のコールバックでfalseを返すと、以下のコールバックも順に呼び出されたりします(ここでは不要なので割愛します)
・plugin_register()に "hookcommand" で登録したコールバック
・plugin_register()に "hookpostcommand" で登録したコールバック

REAPERのあらゆるメインメニュー選択時のActionIDは"hookcommand2"コールバックに飛んでくるので、Extension以外のビルトインメニューに反応して何かする、といったことも出来ちゃいます。
いやもうなんというかやりたい放題できますなぐひひ便利なツールですね。

今回はMainメニュー拡張方法について説明しましたがいかがでしたでしょうか。
ではまた次の記事でお会いしましょう。

当ブログのご利用について(はじめにお読みください)

こんにちはぺろりです。
今日は天気が良くて気持ちが良いです。

この記事はいわゆるはじめにお読みください的なやつです。

当ブログは管理人であるぺろりが、自分で行った実験の記録や勝手な意見やその他もろもろを好き勝手に書いていくというものです。
まずはREAPERに最近ハマっているので、それについてもろもろ書きなぐっていますが、実際のところ
・REAPER拡張などの「仕様」は実はよくわからない(調べきれてないし公開されてるかも不明)
・「実装」がこうなっているっぽくて試したらこう動いた、ということしかわからない
ので、REAPERのバージョンアップなどによって、ある日突然記事にしていたことがまるっとひっくり返って通用しなくなることは普通にあり得ます。
自分が公開したプログラムやノウハウを試したり真似てみたことによってあなたのPCが壊れたり作成中のプロジェクトがおじゃんになったりとかあってもこちらでは責任とれないので明記しときますね。

■免責事項
当ブログに掲載されている記事やもろもろのコンテンツ、リンク等あらゆるものにつきましては、それによっていかなる損失や損害が発生したとしても、当ブログ・管理人は責任を負いかねます。
その他主義・主張・意見・感想などなどあらゆる発言において、効果・効能・正統性や完全性など何も保証するものではありません。あくまですべて一個人の感想レベルのものとして捉えていただき、あとは読者様の判断で自己責任にてご利用頂ければと存じます。

■その他もろもろポリシー
・必要に応じてこの記事は予告なく変更され、ルールが増減することがあります
 ※よほどの事でない限り、申し訳ありませんが変更告知も致しません
・記事のまるまる転載や画像・音源等あらゆるコンテンツも転載を禁止とします
 (転載したい場合は連絡下さいにしようかと思いましたが、やっぱ管理が面倒なので禁止)
・コメントとかでいじめないでくださいね
 ※最悪の場合、コメント出来なくする可能性がありますがそういう暗黒未来は望んでいません
・REAPERはx64版をWindows7,10で使っています。Mac版で使えないネタもあるかもしれませんが、確認が困難なのでごめんなさいということで。

・以上の事がご理解・納得いかない場合は当ブログのご利用はお控え頂ければお互いに幸せかと

そんな感じで皆さん今後ともよろしくお願い致します!

C++ REAPER Extension (2) : DLL名でハマったお話

こんばんはぺろりです。
今回は前回の記事で触れた、DLL名でハマったもう一つのネタを紹介します(忘れないうちに)。

これは筆者のやり方がまずかったというだけなんですが(汗)、自作のExtensionのDLLをどこに置いてテストやデバッグをするかがポイントになります。
筆者はSWSにならって、REAPERのインストールフォルダにあるPluginsフォルダにDLLをコピーして実行&デバッグを行っていました。
(このあたりはSWSのプロジェクト設定(ビルドイベント)を見ると参考になります)

で、しばらくは特に何も問題ありませんでした。

突如問題になったのは、ActionListのいくつかのSectionにActionを追加してみようとしたところからです(今までは通常のMainセクションのみの登録ばかりでした)。
DLLのエントリポイントでActionをMIDIEditorセクションなどに登録しようとするのですが、これも問題なく成功します(ActionIDなどもREAPERから発行されるのをデバッガで確認済み)。
やったー、とREAPERがそのまま起動した後、ActionListでMIDIEditorセクションを見ると
「あれ・・・?」
さっき登録できたActionが一切登録されていない・・・

しばらくいろいろ試した後、同じことをSWSのプロジェクトでやってみると、うまく登録できてしまいました。
このへんでぺろりの頭もだいぶ混乱気味。

仕方がないので食パンを切ってピザトーストにして美味しくいただきました(ただの夕食)。
頭をクリアにしていろいろ筆者のDLLとSWSの何が違うのか検討していった結果、ピンと来ました。
「もしかして・・・実はまだファイル名の規則か何かをミスってるのか?」
試しにSWSと似たような名前(reaper_sws2.dllとか)に変えて試すとうまくいってしまいました。

ここでハッとして Pluginsフォルダを俯瞰してみたところ、
「もしや初期化順・・・!?」

当時自分で作っていたテスト用のDLLはreaper_dbg.dllとかてきとーなファイル名にしていたのですが、これがまずかったようです。
推測ですが、REAPERはプラグインDLLを名前の順に読み込んで初期化を行っている模様。
そして、自分のDLLをreaper_midi.dllよりも前になる名前にしてしまうと、DLLロード時にMIDIEditorセクションに登録したコマンドがreaper_midi.dllロード時に失われてしまうのではないかと。
実際、reaper_midi.dllより後ろになる名前(reaper_nnn.dllとか)にしてやったら問題なくMIDIEditorセクションへAction登録したものが残りました。
これでDLLの名前を変えることで、やっと問題を回避することが出来た訳です。

今回の件はREAPERインストールフォルダのPluginディレクトリにDLLを配置したことによる弊害なので、実はREAPERのリソースフォルダにあるUserPluginsにDLLを配置すれば問題ありませんでした。
(おそらくREAPERのPluginが全て初期化された後にUserPlugins側が読み込まれるのではないかと)

ただ、置き場所や名前に依存して動いたり動かなかったりするプログラムは気持ちが悪いので、あらかじめ今回の問題にひっかからないような名前にしておくのが良いかと思います。
ちなみに自分は"reaper_x"をDLL名のプレフィックスとして付けるようにしています。
(例えば reaper_xpelori.dll など)

ということで、今回はハマった事例の経緯をぐだぐだと説明してしまいましたが、これを見て皆さんはハマらないようにしてくださいね。

このブログについて
ぺろりがREAPERで遊びたいというだけのブログかもしれない

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

twitter: @pelori

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