Extension

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

C++ REAPER Extension (5) : ポーリング処理を行うには?

こんばんは、ぺろりです。
このブログ、最初っからテクニカルな話題ばかりで普通のREAPERユーザーを完全に置いてきぼりにしてる感が半端ないですが、もう少しだけ続きます。

今回はポーリング処理についてですが、その前にhookcommand2コールバックに来るActionIDの話を少し。

REAPERはActionを実行する際、Extensionに対してどのActionがリクエストされてますよ、ということをhookcommand2コールバックで通知してきます。
これによって何かActionが実行されるタイミングだということが「ある程度」検知できます。
何故、「ある程度」なのかというと、実際にActionを実行するExtensionが「実行しましたよ」ということをhookcommand2コールバックで返す(戻り値をtrueで返す)と、そのActionIDの通知はそれ以上他のExtensionへ通知されることはないからです。
恐らくExtensionのDLL登録の順番に依存しているのかもしれません(未検証)が、そういった環境依存で自分のExtensionに通知が来たり来なかったりと動作が変わってしまうのも困ります。

REAPERの組み込みアクションであれば、最後にREAPER本体が処理しそうですが、これにも問題があるようです。
Action実行リクエストの通知はREAPER上のすべての挙動を検知できる訳ではないようで、例えば、Markerは作成(Mキー押下)時にはActionが通知されてきましたが、MarkerをAlt+クリックで削除する際にはAction通知は来ないようでした。
Markerが作成されるタイミングは分かっても、削除されたタイミングが分からないので、自分でMarker情報をもとに何か処理を書く時にだいぶ扱いづらそうなのが予想出来るかと思います。
例えばMarkerの情報をキャッシュしておいて自分のウィンドウに表示等したいが、削除されたタイミングが検知できないとそのキャッシュを更新するタイミングがない、とかでしょうか。
せめてポーリングできるような仕組みがあればなあ・・・

ということで前置きが長くなりましたが、ポーリングで処理を書く方法があるのでそれを紹介します。

■ポーリング処理の実装方法
SWSの実装を参考にすると、IReaperControlSurface::Run() というコールバックメソッドを利用した方法が使えそうです。
ダミーのControlSurfaceクラスを作って利用する感じです。
// DummyのControlSurface
class DummyControlSurface : public IReaperControlSurface
{
  public:
	DummyControlSurface() { }
	virtual ~DummyControlSurface() { }
    
	virtual const char *GetTypeString()   { return ""; } // SWSを参考に
	virtual const char *GetDescString()   { return ""; } // SWSを参考に
	virtual const char *GetConfigString() { return ""; } // SWSを参考に

	// 30FPSかそれくらいで呼ばれるらしい(頻度は未検証)
	virtual void Run()
	{
		// ここでの処理は出来るだけ軽くしておいた方がいいかも。
		// IReaperControlSurfaceには他にも特定のタイミングで
		// 呼ばれるメソッドがあるので、出来るだけそちらで処理して
		// どうしてもダメならここで適当な間隔でポーリングする
	}
};

このクラスのインスタンスを生成し、"csurf_inst"で登録(plugin_register)してやります。
(不要になったらこれまでのコールバック等と同様に "-csurf_inst" で登録解除してあげましょう)
// Run()タイミング検知用ControlSurfaceを生成・登録
DummyControlSurface* csurfInst = new DummyControlSurface();
plugin_register( "csurf_inst", csurfInst );
これで DummyControlSurface::Run() が定期的に呼ばれるようになります。

今回はポーリング処理の書き方について説明しましたがいかがでしたでしょうか。
おそらくこれまでの5回分の記事の解説で、REAPERのExtensionを実装する最低限の準備が揃ったというか、これで大抵のことは無理矢理なんとか実装&問題回避等出来るかもしれません(ホントか?)。

ではまた次の記事でお会いしましょう。

C++ REAPER Extension (4) : Preferencesにページ追加

こんばんはぺろりです。
今日はREAPERのPreferencesに自分のページを追加する方法を説明します。

といってもやるべきことはシンプルで、以下の二つです。
・ページ毎にウィンドウ作成コールバックを実装
・prefs_page_register_t を "prefpage" という名前で登録(plugin_register)
 (この構造体のメモリはどこかに保持しておく)

■Preferencesページ情報の登録
まずはコードを見てみましょう。
prefs_page_register_t prefPageMain; // メインページ用のデータ(どこかに保持しておく)
prefs_page_register_t prefPageChild; // 子供ページ用のデータ(どこかに保持しておく)

// 大項目
memset( &prefPageMain, 0, sizeof(prefPageMain) );
prefPageMain.idstr        = "PREF_PELORI_MAIN"; // ID名(任意)
prefPageMain.displayname  = "Pelori"; // Preferenceに表示される名前
prefPageMain.create       = OnCreatePrefWndMain; // Window生成コールバック
prefPageMain.childrenFlag = true; // 子供がいる
// 登録
plugin_register( "prefpage", &prefPageMain );

// 小項目(子供ページ)
memset( &prefPageChild, 0, sizeof(prefPageChild) );
prefPageChild.idstr        = "PREF_PELORI_CHILD1";
prefPageChild.par_idstr    = prefPageMain.idstr; // 親のIDを指定
prefPageChild.displayname  = "Sub1";
prefPageChild.create       = OnCreatePrefWndChild;
prefPageChild.childrenFlag = false; // 子供はいない
// 登録
plugin_register( "prefpage", &prefPageChild );

ここではメインの大項目とその1階層下に表示できる小項目(子供ページ)を追加しています。
子供ページが不要であれば大項目のページ登録だけでも問題ありません(この場合は prefPageMain.childrenFlag = false にする)。

登録できると以下のような感じにPreferencesウィンドウに表示されることになります。
QS_20180124-022801

次にウィンドウ生成時のコールバック実装ですが、引数に親ウィンドウとなるHWNDが渡されてきますので、このウィンドウに子ウィンドウとしてセットしてやればOKです。
また、呼ばれるタイミングはページの項目を選択した時です(上の例だと"Pelori"や"Sub1"選択時)。
こちらで作ったウィンドウが勝手に破棄されたりはしないようなので、あらかじめウィンドウは作成しておいて、このコールバックで渡されてきたHWNDに毎回子ウィンドウとしてくっつけてやればよいようです。
HWND OnCreatePrefWndMain( HWND hWndParent )
{
	// あらかじめ作っておいたウィンドウのハンドル(ウィンドウ作成まわりは割愛)
	HWND hWndPage;
	// SetParent()の前にWS_CHILDを設定する必要がある
	SetWindowLong( hWndPage, GWL_STYLE,
		 (GetWindowLong(hWndPage, GWL_STYLE) | WS_CHILD) );
	SetParent( hWndPage, hWndParent );
	return hWndPage;
}
小項目(子ページ)側もやることは同様です。

■Preferences情報の登録解除
DLLエントリポイントの終了処理を書くあたりで、prefs_page_register_t のデータの登録解除や自分で作成したウィンドウの破棄等を行います。
plugin_register( "-prefpage", &prefPageChild );
plugin_register( "-prefpage", &prefPageMain );

これでPreferencesウィンドウへのページ追加に関しては完了です。

Preferencesまわりで書ける処理はこの程度のようで、例えば「Preferencesウィンドウが閉じられた」とかいったタイミングを検知する方法は特に提供されていない(?)ようです。
なので、Preferences設定の保存や復帰も自分でいい感じのタイミングに手動で実装する必要があるのではないかと思います。

というところで今回はここまで。
また次回の記事でお会いしましょう。

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で遊びたいというだけのブログかもしれない

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

twitter: @pelori

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