MIDIコントローラースクリプトの作成方法
このページでは、公式・非公式含めて対応していないMIDIコントローラーを独自に対応させる方法についてまとめます。
MIDIコントローラースクリプト自作手順
スクリプトの配置場所
スクリプトは WindowsとmacOSでそれぞれ以下の場所に配置します。
- Windows: `%USERPROFILE%\Documents\Bitwig Studio\Controller Scripts\`
- macOS: `~/Documents/Bitwig Studio/Controller Scripts/`
この場所は自動で下の階層を調べてくれるようなので、作成者やツール単位でフォルダーを作成してその中にスクリプトを配置するのが良さそうです。
ファイル名は必ず「.control.js」という名前で終わる必要があります。
この名前でない場合、スクリプトは認識されません。
スクリプトの雛形の作成と配置
// スクリプトのバージョン指定.
loadAPI(17)
// スクリプトのエラーチェックを有効化.
host.setShouldFailOnDeprecatedUse(true)
// 作成者、対応するMIDIコントローラー名、バージョン、UUIDを設定.
host.defineController("MyCompany", "MyController", "1.0", "UNIQUE-UUID");
// MIDIポートの入力と出力数.
host.defineMidiPorts(1, 0);
// 初期化.
function init() {
// MIDI信号受け取りのコールバックを設定.
var midiIn = host.getMidiInPort(0);
midiIn.setMidiCallback(onMidi);
}
// MIDI信号の受け取り.
function onMidi(status, data1, data2) {
// 受け取ったMIDI信号をデバッグ出力.
println("MIDI Received - Status: " + status + ", Data1: " + data1 + ", Data2: " + data2);
}
function exit() {
// 終了時の処理
}
- 「host. host.defineController」の部分には、MIDIコントローラーの会社名、MIDIコントローラー名、バージョン、UUIDを正しく指定する必要があります
- 配布しないのであれば何を指定しても問題ないですが、できればわかりやすい名前を指定しておいたほうが良いです
- 特にUUIDは、ユニークなIDでなければいけないので、UUID生成ツールであらかじめ作っておきます
MIDIコントローラーとして登録
設定画面から作成したMIDIコントローラースクリプトを登録します。
「コントローラー」から「+Add Controller」を選択。
"Hardwave Vendor" に先ほどのスクリプトであれば「MyCompany」の部分、"Product" から先程のスクリプトであれば「MyController」の部分を選択して「Add」をクリック。
- 上記画像はベンダー名とプロダクト名を変更しています
- ベンダー名 "M-Audio"、プロダクト名 "M-Audio Keystation Mini 32 MK3"
そして利用するMIDIコントローラーを指定します。
- ※ここでは「Keystation Mini 32 MK3」を選びました
スクリプトコンソールの表示
スクリプトコンソールを表示する方法が見つからなかったので、ショートカットキーに登録します。
設定画面から「ショートカット」を選び「スクリプト」で検索。「表示 - コントロールスクリプトコンソール」をショートカットキーに登録します。
Javascriptなので、[ALT+J] などが良いかもしれません。
macOSでは[CTRL+D] が空いていたので、それを使っても良いと思います。
ログを有効にすると以下の出力がされました。
もしエラーが出ている場合は、その情報をもとに修正しなければいけません。
正しく設定されていれば、MIDIコントローラーの押したMIDI信号に対する情報がコンソールに出力されます。
そこで押したMIDIのキーやボタンがどの信号に該当するのかを調べます。
上記画像であれば「64」が処理したいMIDI番号です。この信号を Bitwigの操作に割り当てます。
MIDI番号「64」を再生ボタンに割り当ててみる
再生ボタンに割り当てるには Bitwig の Transportオブジェクトが必要です。
そのため、init() で Transportを生成して変数に格納するようにします。
// transport変数.
var transport;
// 初期化.
function init() {
// MIDI信号受け取りのコールバックを設定.
var midiIn = host.getMidiInPort(0);
midiIn.setMidiCallback(onMidi);
// Transportを生成.
transport = host.createTransport();
// 初期化完了メッセージ.
host.showPopupNotification("Init: Keystation Mini 32 MK3");
}
"transport" 変数に格納したので、onMidi() で再生処理を呼び出します。
// MIDI信号の受け取り.
function onMidi(status, data1, data2) {
// 受け取ったMIDI信号をデバッグ出力.
println("MIDI Received - Status: " + status + ", Data1: " + data1 + ", Data2: " + data2);
if(status == 176 && data1 == 64) {
// 再生.
transport.togglePlay();
println("transport play triggered!");
}
}
これで MIDI番号「64」で再生と停止をトグルするスクリプトが実装できました。
単に再生と停止を割り当てるだけであれば、ショートカット設定から行うのが楽です。
再生と停止をするスクリプト
修正したスクリプトの再読み込み
修正したスクリプトの再読み込みは Bitwig を再起動するしかなさそうな印象です…。(またはMIDIコントローラースクリプトを再登録する)
その他メモ
コントロールサーフェスプロジェクトから作成する (※未検証)
古い情報だと「CTRL+ENTER」で Control Surface Project を作成できる…という情報がありますが、どうもそのショートカットキーは削除されている印象です。
なので、「ショートカット」の設定から「コントロール」で検索してショートカットキーを登録して、作成画面を呼び出します。
MIDIキーボードのVelocityを固定化する
BitwigはMIDIキーボードのVelocityカーブを直接指定するオプションが用意されていません。
一応、
Note FXデバイスには
Velocity Curveがあり、それによってVelocityを固定化できますが、毎回設定するのは不便です。
そのため標準でVelocityを固定化するには、以下のスクリプトを用意します。
var noteInput;
function init() {
var midiIn = host.getMidiInPort(0);
midiIn.setMidiCallback(onMidi);
noteInput = midiIn.createNoteInput("Fixed Velocity Input");
noteInput.setShouldConsumeEvents(false); // コールバックと併用する場合必須
}
function onMidi(status, data1, data2) {
var FIXED_VELOCITY = 100; // 任意の固定値(1~127)
// ノートオン: 0x90~0x9F, ノートオフ: 0x80~0x8F
var isNoteOn = (status & 0xF0) === 0x90 && data2 > 0;
var isNoteOff = (status & 0xF0) === 0x80 || ((status & 0xF0) === 0x90 && data2 === 0);
if (isNoteOn) {
// ノートオンのvelocityを固定値に
noteInput.sendRawMidiEvent(status, data1, FIXED_VELOCITY);
} else if (isNoteOff) {
// ノートオフはvelocity 0で送信(または元の値を使う)
noteInput.sendRawMidiEvent(status, data1, 0);
} else {
// それ以外のMIDIメッセージはそのまま
noteInput.sendRawMidiEvent(status, data1, data2);
}
}
注意点として、MIDI録音時にはこの値は反映されず、本来のVelocityが記録されます。
これはBitwigの仕様によるものです。
ループ区間内で再生開始ヘッドを動かす
以下のスクリプトを使うと、ツマミを回すとループ区間内で再生開始ヘッドが動かせるようになります。(※デバイス登録周りやMIDI信号のコールバックの呼び出し記述は省略しています)
// transport変数
var transport;
// ループ位置.
var loopStart = 0; // 開始.
var loopLength = 0; // ループの長さ.
// 初期化.
function init() {
var transport = host.createTransport();
transport.arrangerLoopStart().addValueObserver(function(value) {
loopStart = value; // 開始位置のコールバック。小節単位
});
transport.arrangerLoopDuration().addValueObserver(function(value) {
loopLength = value; // ループの長さのコールバック。小節単位
});
}
// ループ区間の再生ヘッドの移動.
// @param ratioは0.0〜1.0の想定.
function movePlayheadWithinLoop(ratio) {
//if(transport.isArrangerLoopEnabled() == false) {
// TODO: ループ区間が無効の場合はプロジェクト全体の長さから設定した方が良いかもしれない.
//}
if(ratio < 0) { ratio = 0; } // 念のため範囲チェック
if(ratio > 1) { ratio = 1; }
var newPosition = loopStart + loopLength * ratio
transport.setPosition(newPosition);
}
現在選んでいるトラックの音量を変更する
現在選んでいるトラックの音量を変更するスクリプトです。
MIDI Learnでも実現できますが、スクリプトで作っておくと環境移行時に登録の手間を減らせます。
ボリュームノブの位置が現在設定されているdBよりも高い場合、初回は認識しないようです。
dBを下回る値になるとフェーダーが動き始めます。
CursorTrackを生成するとトラックの情報を変更できるようです。
// 現在選んでいるトラック情報.
var cursorTrack;
function init() {
// 現在選択中のトラックを取得
cursorTrack = host.createCursorTrack("CursorTrack", "Cursor Track", 0, 0, true);
// コールバックの受け取りを定義する
// ...
}
// 現在のトラックの音量を設定
function setCurrentTrackVolume(ratio) {
if(ratio < 0) { ratio = 0; }
if(ratio > 1) { ratio = 1; }
cursorTrack.volume().set(ratio);
}
再生ヘッドを前/次のキューマーカーに移動して名前を表示する
キューマーカーの移動をわかりやすくするために、マーカー名を表示する機能を作るスクリプトの例です。
マーカー情報を更新するためのObserver周りの処理が少しややこしいかも…。
// transport変数.
var transport;
// arranger変数.
var arranger;
// cue marker bank変数.
var cueMarkerBank;
// 現在の再生位置キャッシュ.
var currentTransportPos = 0;
// ジャンプ後にマーカー表示を行うフラグ.
var pendingShowMarker = false;
// マーカー数は最大64で想定.
const MARKER_COUNT = 64;
const markerCache = []; // キャッシュ.
// スクリプトのバージョン指定.
loadAPI(20);
// スクリプトのエラーチェックを有効化.
host.setShouldFailOnDeprecatedUse(true);
// 作成者、対応するMIDIコントローラー名、バージョン、UUIDを設定.
host.defineController("Behringer", "X-Touch mini", "1.0", "260ad644-4909-ef58-6a95-73c1c2f9b41c");
// MIDIポートの入力と出力数.
host.defineMidiPorts(1, 1);
// 初期化.
function init() {
// MIDI信号受け取りのコールバックを設定.
var midiIn = host.getMidiInPort(0);
midiIn.setMidiCallback(onMidi);
// Transportを生成.
transport = host.createTransport();
// 再生位置をObserverで監視してキャッシュ.
transport.getPosition().addValueObserver(function(pos) {
currentTransportPos = pos;
println("Transport Pos Updated: " + currentTransportPos);
// ジャンプ後に位置更新が来たタイミングでマーカー名を表示.
if (pendingShowMarker) {
pendingShowMarker = false;
showCurrentMaker();
}
});
// Arrangerを生成.
arranger = host.createArranger();
// Arrangerから Cue marker bankを生成.
cueMarkerBank = arranger.createCueMarkerBank(MARKER_COUNT);
for (var i = 0; i < MARKER_COUNT; i++) {
markerCache[i] = {
name: "",
position: -1
};
const marker = cueMarkerBank.getItemAt(i);
const index = i;
// 名前取得 (Observerで更新).
marker.name().addValueObserver(function(name) {
markerCache[index].name = name;
});
// 位置取得 (Observerで更新).
marker.position().addValueObserver(function(pos) {
markerCache[index].position = pos;
});
}
// 初期化完了メッセージ.
host.showPopupNotification("Init: X-Touch mini");
}
// MIDI信号の受け取り.
function onMidi(status, data1, data2) {
// 受け取ったMIDI信号をデバッグ出力.
println("MIDI Received - Status: " + status + ", Data1: " + data1 + ", Data2: " + data2);
if (status == 154 && data1 == 18) {
pendingShowMarker = true; // マーカー表示の更新.
transport.jumpToPreviousCueMarker(); // 1つ前のキューに移動.
}
else if (status == 154 && data1 == 19) {
pendingShowMarker = true; // マーカー表示の更新.
transport.jumpToNextCueMarker(); // 次のキューに移動.
}
}
// 現在のマーカーの表示.
function showCurrentMaker() {
var currentPos = currentTransportPos;
println("currentPos: " + currentPos);
var currentMarker = null;
// 再生ヘッドの位置以前で最も近いマーカーを探す.
for (var i = 0; i < markerCache.length; i++) {
const m = markerCache[i];
println("" + i + ":" + m.name + " - " + m.position);
if (m.position >= 0 && m.position <= currentPos) {
if (currentMarker == null || m.position > currentMarker.position) {
currentMarker = m;
}
}
}
if (currentMarker && currentMarker.name) {
// ポップアップでマーカー名を表示.
host.showPopupNotification("Marker: " + currentMarker.name);
// デバッグ用に元の名前もログ出力.
println("Current Marker: " + currentMarker.name);
}
}
function exit() {
// 終了時の処理
}
Remote Controlの値を MIDIコントローラに送受信する
MIDI Learnの場合、MIDIコントローラー側へパラメータを送る方法がわからなかったので、自作してみました。
以下は Behringerの "X-Touch mini" での想定です。
// transport変数.
var transport;
var midiOut;
var cursorTrack;
var cursorDevice;
// Remote Control.
const KNOB_COUNT = 8; // 8個想定.
var remotePage;
// スクリプトのバージョン指定.
loadAPI(20);
// スクリプトのエラーチェックを有効化.
host.setShouldFailOnDeprecatedUse(true);
// 作成者、対応するMIDIコントローラー名、バージョン、UUIDを設定.
host.defineController("Behringer", "X-Touch mini", "1.0", "260ad644-4909-ef58-6a95-73c1c2f9b41c");
// MIDIポートの入力と出力数.
host.defineMidiPorts(1, 1);
// 初期化.
function init() {
// MIDI信号受け取りのコールバックを設定.
var midiIn = host.getMidiInPort(0);
midiIn.setMidiCallback(onMidi);
// midi outを取得.
midiOut = host.getMidiOutPort(0);
cursorTrack = host.createCursorTrack("MyCursorTrack", "MyCursorTrack", 0, 0, true);
cursorDevice = cursorTrack.createCursorDevice("MyCursorDevice", "MyCursorDevice", 0, CursorDeviceFollowMode.FOLLOW_SELECTION);
remotePage = cursorDevice.createCursorRemoteControlsPage(8);
// Remote Controlの値を8個のリングに送る.
for(var i = 0; i < KNOB_COUNT; i++) {
bindRing(i, i+1);
}
// 初期化完了メッセージ.
host.showPopupNotification("Init: X-Touch mini");
}
// MIDI信号の受け取り.
function onMidi(status, data1, data2) {
// 受け取ったMIDI信号をデバッグ出力.
println("MIDI Received - Status: " + status + ", Data1: " + data1 + ", Data2: " + data2);
if ((status & 0xF0) !== 0xB0) {
return; // 不要なステータスは除外.
}
// CC1~CC8 を Remote Control 0~7 に対応させる例
if (data1 >= 1 && data1 <= 8) {
var paramIndex = data1 - 1;
remotePage.getParameter(paramIndex).set(data2, 128);
}
}
// X-Touch MiniのLEDリングの送信.
function bindRing(paramIndex, ccNumber) {
remotePage.getParameter(paramIndex).value().addValueObserver(128, function(v) {
midiOut.sendMidi(0xBA, ccNumber, v);
});
}
現在のデバイスのポップアップブラウザを開いてプリセット選択する
CusorTrack -> CursorDevice から replaceDeviceInsertionPoint() を呼び出すと、InsertionPointが得られるので、browse() でポップアップブラウザが表示できます。
// InsertionPointを取得. (関数名的には置き換え扱いらしい...)
var insertionPoint = cursorDevice.replaceDeviceInsertionPoint();
// ポップアップブラウザを開く.
insertionPoint.browse();
なお、ポップアップブラウザの制御は、createPopupBrowser() で生成したPopupBrowserインターフェースで行います。
技術資料
APIリファレンスを見る方法
設定画面から「ヘルプ」を選ぶと、APIリファレンスへのリンクがあります。
なぜかWeb上には公開されていないようなので、この場所からドキュメントを開いて調べる必要があります。
BitwigデバイスのUUIDを調べる方法
Bitwigデバイスをスクリプトから追加したい場合、CursorTrack -> CursorDevice -> DeviceChain で、DeviceChainを取得して、startOfDeviceChainInsertionPoint() などでデバイス追加ができるのですが、戻り値の InsertionPoint.insertBitwigDevice() でBitwigデバイスを追加するには UUID が必要です。
BitwigデバイスのUUIDを調べるには config.jsonに開発者用の設定をする必要があります。
- Windows: %USERPROFILE%\AppData\Local\Bitwig Studio\config.json
- Mac: ~/Library/Application Support/Bitwig/Bitwig Studio/config.json
- Linux: ~/.BitwigStudio/config.json
上記のパスに config.jsonを配置します。
// config.json
{
"can-copy-device-and-param-ids": true
}
するとデバイス右クリックで "Copy Device Id to Clipboard" という項目が表示されるのでこれを選択。
例えば Polymer であれば "8f58138b-03aa-4e9d-83bd-a038c99a4ed5" という値が得られました。
あとはjava.util.UUID.fromString()にこの値を渡すと挿入ができるようになります。
// Polymerをデバイスチェーンの先頭に追加.
var insertionPoint = deviceChain.startOfDeviceChainInsertionPoint();
// UUIDを変換.
var deviceUuid = java.util.UUID.fromString("8f58138b-03aa-4e9d-83bd-a038c99a4ed5");
// 挿入実行.
insertionPoint.insertBitwigDevice(deviceUuid);
デバイスの追加は createSpecificBitwigDevice() や createSpecificVst[2/3]Device() あたりでもできそうです (未検証)
自作スクリプト
自作したMIDIコントローラースクリプトを置いておきます。
関連ページ
最終更新:2026年04月16日 07:30