REAPERのプロジェクトタブを切り替えるReaScriptを書いたら車輪のアンコールだった

概要

REAPERのプロジェクトタブを、WebブラウザみたいにCtrl+Tabでサクサク切り替えられたらな~~。
→ でもアクションリストにはなぜかそういうアクションが見当たらないんだよな~~。
→ よし作るか。
→ 作った。
→ あっよく見たらアクションリストに元からちゃんとそういうのあった。
→ 車輪の再発明おめでとう。永年車庫入り。死。
→ いや、死なすぐらいならReaScript基礎編記事としてQiitaにでも突っ込むか。←今ここ

実装

とりあえず一番のコア部分はこれ。

lib\Select project tab.lua(下にも再掲します)
function SelectProjectTabByStep(step)

  local function prj(i) return reaper.EnumProjects(i, "") end

  local i, i_active = -1, -1
  local prj_active = prj(-1)
  repeat
    i = i + 1
    i_active = prj_active==prj(i) and i or i_active -- 今のプロジェクトが何番目のタブなのか把握
  until not prj(i+1)
  local i_max = i
  local i_new = (i_active+step) % (i_max+1) -- インデックス外なら回り込む
  reaper.SelectProjectInstance(prj(i_new))

end

使ったReaScript関数は2つ。

  • reaper.EnumProjects
  • reaper.SelectProjectInstance

reaper.EnumProjects

ReaProject retval, string projfn = reaper.EnumProjects(integer idx, string projfn)

idx=-1 for current project,projfn can be NULL if not interested in filename. use idx 0x40000000 for currently rendering project, if any.

idxに何番目のプロジェクトかを指定すると、ReaProjectとかいうデータ型(内蔵デバッガの表示はreaper object)でそのプロジェクトが帰ってくる。
インデックスは一番左のタブから0始まりで数え、また特別に現在開いているプロジェクトは-1で指定できる。…というか、インデックス範囲より上だったり下だったりするような、はみ出した数を入れたらとにかく現在のものになるっぽい。なのでインクリメントして端まで達しても勝手に回り込んだりはしてくれない。あと少なくとも整数型である必要があるらしく、色々試したけど整数以外はエラーになった。

image.png

-- 上記のような状態で、
prj = reaper.EnumProjects(-1, "")    -- 現在のタブ(2個目のタブ)
prj = reaper.EnumProjects(0, "")     -- 1個目のタブ
prj = reaper.EnumProjects(-3776, "") -- 現在のタブ(-3775個目のタブは無いので)
prj = reaper.EnumProjects(3.14, "")      -- "number has no integer representation"
prj = reaper.EnumProjects("Mt.Fuji", "") -- "number expected, got string"
prj = reaper.EnumProjects(nil, "")       -- "number expected, got nil"

projfnはファイル名だけど今回は関係ない。
なおリファレンスにはprojfn can be NULLとあるが、少なくとも文字列型になり得る値である必要があるらしく、サボったりnilにしたりするとエラーになった。

prj = reaper.EnumProjects(-1, "")   -- OK
prj = reaper.EnumProjects(-1, 3776) -- OK
prj = reaper.EnumProjects(-1)      -- "string expected, got no value"
prj = reaper.EnumProjects(-1, nil) -- "string expected, got nil"

reaper.SelectProjectInstance

reaper.SelectProjectInstance(ReaProject proj)

REAPERのプロジェクトを指定するとそのインスタンスを開く。

なお、ReaScriptでは何をするにしてもほぼ必ずこのReaProjectを指定する。というか、例えばメディアをいじるためにはまずそのメディアの取得が必要になり、そのためにはそのメディアがあるトラックを取得し、そのためにはそのトラックがあるプロジェクトを取得……という感じで、何をするにも結局親玉のプロジェクトを掴んでやらないことには始まらない仕組みになっている。気がする。
で、実際そのためにいちいちReaProject型データを頑張って掴むのはめんどいので、特別に0を入れてやると自動的に現在のアクティブなプロジェクトを指定することができるようになっている。例えばプロジェクトのメディアアイテム数を数えるreaper.CountMediaItems説明を見ると、

integer reaper.CountMediaItems(ReaProject proj)

count the number of items in the project (proj=0 for active project)

とある通り、便利。
…色々試したところ、実は0に限らず割とどんな雑な値でもとにかく現在開いているプロジェクトが指定されるっぽい。まあ0にせよと言われてるんだから素直にそうすれば良いんだけど。

i = reaper.CountMediaItems(reaper.EnumProjects(0, "")) -- 1個目のタブ
i = reaper.CountMediaItems(0)         -- 現在のタブ(推奨)
i = reaper.CountMediaItems(3776)      -- 現在のタブ
i = reaper.CountMediaItems(3.14)      -- 現在のタブ
i = reaper.CountMediaItems("Mt.Fuji") -- 現在のタブ
i = reaper.CountMediaItems(nil)       -- 現在のタブ
i = reaper.CountMediaItems()          -- 現在のタブ

あと、このproj=0 for active projectという説明は必ずしも全てのReaProject型引数を要する関数の説明に書かれているわけでもなければ、基礎事項としてリファレンス全体の頭とかに書かれているわけでもないので、うっかりド忘れすると困る(困った)。今回のreaper.SelectProjectInstanceにもそういう説明はない
参考までに、Chromeで開いたLua版リファレンスページ内を0 for active projectで検索したら結果は10件、Reaproject projで検索したら106件だった。

あとは書く

REAPERリソースフォルダ(REAPER.iniがあるフォルダ)のScriptフォルダ内に自分用のフォルダPhroneris(私の横文字HNです)を作り、更にその中にlibフォルダを作って複数のスクリプトで共用するスクリプトの置き場とした。

lib\Select project tab.lua(再掲)
function SelectProjectTabByStep(step)

  local function prj(i) return reaper.EnumProjects(i, "") end

  local i, i_active = -1, -1
  local prj_active = prj(-1)
  repeat
    i = i + 1
    i_active = prj_active==prj(i) and i or i_active -- 今のプロジェクトが何番目のタブなのか把握
  until not prj(i+1)
  local i_max = i
  local i_new = (i_active+step) % (i_max+1) -- インデックス外なら回り込む
  reaper.SelectProjectInstance(prj(i_new))

end

そして以下の2つをアクションから読み込んでショートカットを割り当てて完了。
あっそうそう、libからスクリプトを引っ張ってくるためにreaper.GetResourcePathREAPERのリソースフォルダのパスを取得する

Phroneris_Switch to next project tab.lua
-- こいつにCtrl+Tabを割り当て(既存の重複ショートカットは解雇)
package.path = reaper.GetResourcePath().."\\Scripts\\Phroneris\\lib\\?.lua"
require "Select project tab"
SelectProjectTabByStep(1)
Phroneris_Switch to previous project tab.lua
-- こいつにCtrl+Shift+Tabを割り当て(既存の重複ショートカットは解雇)
package.path = reaper.GetResourcePath().."\\Scripts\\Phroneris\\lib\\?.lua"
require "Select project tab"
SelectProjectTabByStep(-1)

ちなみに実際のコードには日本語コメントは一切含めていない。REAPERのスクリプトエディタは、マルチバイト文字(非ラテン文字?)をまともに表示できないので…。

成果

prjtab4.gif
(キー入力表示:tinyosdkey

作った動機と顛末

あれは、自作の製品版REAPER日本語化パッチ(森)がver 5.70.001の頃――

「REAPERのプロジェクトタブを、WebブラウザみたいにCtrl+Tabでサクサク切り替えられたらな~~」
「まあ普通に考えてそんな基礎的なアクションはまず間違いなく内蔵してるはずだから、探してショートカット割り当てれば良いよな。アクションリストで…next tabっと」

image.png

「…? Docker:っつってるしこれはドックの方のタブだよね。こんだけ?」
「え、じゃあtabならタブ関連の全部のアクションが出るかな」

image.png

「……??」

プロジェクトタブの切り替え、無いの???????


image.png

「まあ……無いなら作るか」


そして完成。

「できた~。さて、それはそれとしてREAPER日本語化パッチの翻訳も進めるか」
「おや? 気になるブログが」
「ん~なになに、現行の日本語化パッチではアクションリストが中途半端にローカライズされるので内蔵アクションを探し難くなる…?」
「…………」

prjtab5-1.png

prjtab5-2.png

(日本語化パッチver5.70.002で英語検索に対応させました)
image.png

おしまい。

執筆時点の環境

  • Windows 7 64bit
  • REAPER v5.78/x64, v5.79pre7/x64
  • REAPER APIリファレンス Generated by REAPER v5.70/linux-arm

リンク

REAPER | ReaScript
REAPER API functions
REAPERの日本語化パッチのお話 : ペロリーパー | PeloREAPER
製品版REAPER日本語化パッチ(森)

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.