見出し画像

MIDI コンで MATLAB / Simulink をコントロール ~ フィジカル・デバイスで MATLAB を制御する


まえがき


MIDI(Musical Instrument Digital Interface)とは、電子楽器等を相互接続するための世界統一規格です。

MIDI コン(トローラー)は、MIDI 信号を送(受)信できる物理インターフェースで、MIDI 音源を簡易的に演奏したり、DAW(Digital Audio Workstation)ソフトウェアを制御するために用いられます。


それと MATLAB に何の関係が?と思われる方もいるかもしれませんが、MATLAB / Simulink では外部 MIDI コントローラーを用いて、パラメータの調整を簡単に行うことができます。

Simulink では DSP System Toolbox、MATLAB では Audio Toolbox が必要です。(Audio Toolbox の動作には DSP System Toolbox、Signal Processing Toolbox も必要です)


今回は MIDI コンとしては大変ポピュラーな KORG の nanoKONTROL2 を使って、実際の例を見ていきましょう。

昔から、デモアプリをこれでコントロールしたりよくやっていました。


準備


MIDI コントローラーを PC に接続してから MATLAB を起動し、コマンドラインで

>> [ccInfo,deviceName] = midiid

としてコントローラーのどれかを操作します。

ccInfo =

        1064


deviceName =

    'nanoKONTROL2 1 SLIDER/KNOB'

などと出れば準備完了です。

floor(ccInfo/1000) が MIDIチャネル、下3桁が CC(コントロール・チェンジ)ナンバーになります。

CC ナンバーは各フェーダーやボタン等に個別に割り当てられています。

デフォルトでオムニ・オン(全 MIDI チャネル受信可)になっているので、MIDI チャネルを気にする必要はありません。

>> setpref('midi','DefaultDevice',deviceName)

でデフォルト MIDI デバイスを設定できます。

設定しない場合は OS で自動的に選択されるので想定外の動きとなる場合があります。名前が見つからない場合はデフォルト設定となるのか、適当な名前を入れても動いてしまうことがあります。

その場合、環境が変わると動かなくなることもあり得ますので注意しましょう。

Windows 標準ドライバーでも動作するようですが、後述する "KONTROL Editor" を使うには純正ドライバーが必要です。上記 deviceName は純正ドライバーを入れた Windows の場合です。

使うデバイスが変わらない限り、以下のスクリプトを一度走らせておけば MATLAB のシステム設定として記録されます。

availableDevices = mididevinfo;
inputMIDIDevices = {availableDevices.input.Name};
idx = contains(inputMIDIDevices,'nanoKONTROL2');  % find nanoKONTROL2
inputDeviceName = inputMIDIDevices{idx};
setpref('midi','DefaultDevice',inputDeviceName)


また、以下のように入出力をまとめて扱うこともできます。
入出力それぞれの関数の引数に共通で "device" が使えます。

これを setpref はできないようです。

availableDevices = mididevinfo;

inputMIDIDevices = {availableDevices.input.Name};
idx = contains(inputMIDIDevices,'nanoKONTROL2');
inputDeviceName = inputMIDIDevices{idx};
% inputDevice = mididevice(inputDeviceName);

outputMIDIDevices = {availableDevices.output.Name};
idx = contains(outputMIDIDevices,'nanoKONTROL2');
outputDeviceName = outputMIDIDevices{idx};
% outputDevice = mididevice(outputDeviceName);

device = mididevice('Input',inputDeviceName,'Output',outputDeviceName)


MIDI インプリメンテーション・チャート


MIDI を使って色々やりたい場合はまず、その機器の「MIDI インプリメンテーション・チャート」を入手します。

コントローラーの各コンポーネントへの、MIDI の割り当てなどが記載されています。

キーボードや音源であれば通常、マニュアルに記載があります。
ない場合は、メーカーサイトで配布しているはずです。

nanoKONTROL2 の場合、pdf 版はなぜか概略しか載っていないため、テキスト版を検索して入手します。

MATLAB で使う場合、冒頭で示したように midiid してコントローラーを操作すればすぐ割り当てが分かるので特に必要というわけではありませんが、一覧で見られるので便利かとは思います。

今回使う部分を以下に示します。

MIDI インプリメンテーション・チャート

"value(0-127)" は、MATLAB では正規化されて [0,1] で受信されます。


Simulink で使う


とりあえず、モデルに "MIDI Controls" ブロックを置くだけで使えます。

モデル例

以下は、スライダー(任意)を適当に動かした場合の例です。

Scope 画面

MIDI とは関係ありませんが、フレーム信号を表示する場合は、Scope の設定もフレームにするのを忘れないようにしてください。

Scope プロパティ


特定のコンポーネントで動作させたい場合は "MIDI Controls" ブロックの設定を開き、使用したい CC ナンバーを割り当てます。

左から2番目のスライダーを割り当てた例


これだけで、Simulink モデル上のパラメータを MIDI コントローラーで制御することが可能になります。


パラメトリック・イコライザー


Audio Toolbox を使った簡単な例として、あらかじめ用意されているパラメトリック・イコライザーを使ってみましょう。

以下をコマンドラインか Live Editor で実行します。

>> parametricEQPlugin = audiopluginexample.ParametricEqualizerWithUDP;
>> configureMIDI(parametricEQPlugin)

「警告: 'Data' 値が table 配列の場合、'ColumnFormat' 値は効果がありません。」が出ても気にしないでください。(R2024bPre では出ません)

しばらくすると、設定用 UI が表示されます。
(一覧表示されるのは R2023a 以降)

configureMIDI

ここでコントロールしたいプロパティを選択し、それに割り当てたいコントローラーを動かすと設定されます。

設定例

なぜか単位が(個)になっていますがこれも気にしないでください。
(報告済み)


 MATLAB コードを生成 -> OK

で設定用スクリプトが生成されます。

function setupMIDIControls(obj)
% Set up MIDI connections for audiopluginexample.ParametricEqualizerWithUDP.
% Use this code to synchronize your object with the MIDI device.

configureMIDI(obj,'PeakGain1',1000,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'CenterFrequency1',1019,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'QualityFactor1',1016,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'PeakGain2',1001,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'CenterFrequency2',1020,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'QualityFactor2',1017,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'PeakGain3',1002,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'CenterFrequency3',1021,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'QualityFactor3',1018,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');


>> parameterTuner(parametricEQPlugin)

で GUI が表示され、コントローラーを動かせば動作が確認ができます。

parameterTuner


以下のスクリプトをライブエディターで実行すると、実際に MIDI コントローラーで音を変えることができます。

fileReader = dsp.AudioFileReader('Filename','RockDrums-44p1-stereo-11secs.mp3');
fileReader.PlayCount = 3;  % 再生繰返し回数
deviceWriter = audioDeviceWriter;
% parametricEQPlugin = audiopluginexample.ParametricEqualizerWithUDP;

while ~isDone(fileReader)
    input = fileReader();
    output = parametricEQPlugin(input);
    deviceWriter(output);
    drawnow limitrate;
end

release(fileReader);
release(deviceWriter);
disconnectMIDI(parametricEQPlugin)


全体をまとめたスクリプトを以下に示します。

Live Editor で実行できます。

clear

function setupMIDIControls(obj)
% Set up MIDI connections for audiopluginexample.ParametricEqualizerWithUDP.
% Use this code to synchronize your object with the MIDI device.

configureMIDI(obj,'PeakGain1',1000,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'CenterFrequency1',1019,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'QualityFactor1',1016,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'PeakGain2',1001,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'CenterFrequency2',1020,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'QualityFactor2',1017,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'PeakGain3',1002,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'CenterFrequency3',1021,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
configureMIDI(obj,'QualityFactor3',1018,'DeviceName','nanoKONTROL2 1 SLIDER/KNOB');
end

fileReader = dsp.AudioFileReader('Filename','RockDrums-44p1-stereo-11secs.mp3');
fileReader.PlayCount = 5;
deviceWriter = audioDeviceWriter;
parametricEQPlugin = audiopluginexample.ParametricEqualizerWithUDP;
setupMIDIControls(parametricEQPlugin)
parameterTuner(parametricEQPlugin)

while ~isDone(fileReader)
    input = fileReader();
    output = parametricEQPlugin(input);
    deviceWriter(output);
    drawnow limitrate;
end

release(fileReader);
release(deviceWriter);

disconnectMIDI(parametricEQPlugin)

操作例


Live Editor でグラフの色を変える


コントローラーの左3本のスライダーをそれぞれ H,S,V に割り当て、グラフの色を変えてみましょう。

左端の "R" ボタンを押しながらスライダーを操作したときだけ色が変わるようにしています。

Live Editor で以下を実行します。

MIDIcontrols = [0x00:0x07, 0x40:0x47, 0x29:0x2D, 0x30:0x37, 0x20:0x27];  % Sliders, Rs, Commands, Ms, Ss
midicontrolsObject = midicontrols(MIDIcontrols,[0, 0, 1, zeros(1,length(MIDIcontrols)-3)]);  % H, S, V, ~

f = figure(1);
f.Visible = "on";
f.CloseRequestFcn = "midicallback(midicontrolsObject,[]); delete(f)";
ax = gca;
rect = rectangle(ax,'Position',[4 3 5 5], Curvature = 1, FaceColor="w");
axis(ax,"equal")
axis(ax,"off")

midicallback(midicontrolsObject,@(midiObj) read_MIDI(midiObj, rect));

function read_MIDI(midiObj, rect)

MIDI_Sliders = 1:8;
MIDI_R_Buttons = 9:16;
% MIDI_PLAY = 17;
% MIDI_STOP = 18;
% MIDI_REW = 19;
% MIDI_FF = 20;
% MIDI_REC = 21;
% MIDI_M_Buttons = 22:29;
% MIDI_S_Buttons = 30:37;

% read MIDI controller
mididata = midiread(midiObj);
R1_on = mididata(MIDI_R_Buttons(1));

if (R1_on == 1)
    H = mididata(MIDI_Sliders(1));
    S = mididata(MIDI_Sliders(2));
    V = mididata(MIDI_Sliders(3));
    col = hsv2rgb(H,S,V);
    rect.FaceColor = col;
end
end

実行例


App Designer アプリ化


上の Live Editor 版にスライダー表示も追加してアプリ化してみましょう。


-「設計ビュー」で「座標軸」をドロップ
-「スライダー」を3つドロップし、ラベルをそれぞれ H,S,V に
 Limits を 0,1 に
 (お好みで Orientation を縦に)

設計ビュー


-「コードブラウザー」でアプリ中で保持する必要のあるプロパティを追加 
 rect
 midicontrolsObject
-「コードビュー」「コンポーネント ブラウザー」の
 アプリ名右クリック -> コールバック ->Start Up コールバックの追加

    properties (Access = private)
        rect
        midicontrolsObject
    end
    
    methods (Access = private)    
        function read_MIDI(app, midiObj)
            MIDI_Sliders = 1:8;
            MIDI_R_Buttons = 9:16;

            mididata = midiread(midiObj);
            R1_on = mididata(MIDI_R_Buttons(1));

            if (R1_on == 1)
                H = mididata(MIDI_Sliders(1));
                S = mididata(MIDI_Sliders(2));
                V = mididata(MIDI_Sliders(3));

                app.HSlider.Value = H;
                app.SSlider.Value = S;
                app.VSlider.Value = V;
                
                col = hsv2rgb(H,S,V);
                app.rect.FaceColor = col;
            end
        end
    end
        function startupFcn(app)
            MIDIcontrols = [0x00:0x07, 0x40:0x47, 0x29:0x2D, 0x30:0x37, 0x20:0x27];  % Sliders, Rs, Commands, Ms, Ss
            app.midicontrolsObject = midicontrols(MIDIcontrols,[0, 0, 1, zeros(1,length(MIDIcontrols)-3)]);  % H, S, V, ~

            app.HSlider.Value = 0;  app.SSlider.Value = 0;  app.VSlider.Value = 1;

            app.rect = rectangle(app.UIAxes,'Position',[4 3 5 5], Curvature = 1, FaceCwlor="w");
            axis(app.UIAxes,"equal")
            axis(app.UIAxes,"off")
            title(app.UIAxes,"MIDI HSV")

            midicallback(app.midicontrolsObject,@app.read_MIDI);
        end

実行例

このように、MIDI コンを使えば複数のパラメータを同時に動かすこともできて便利です。


今回はアプリ上のスライダーの動作設定は何もしていません。

アプリ上では動かないようにしたり、逆にアプリ上でも操作できるようにしたい場合は、それぞれコールバック関数を追加して設定してください。


コントローラーの LED を光らせる


一般的に MIDI コントローラーは一方向の場合も多いのですが、nanoKONTROL2 は各 LED をコントロールメッセージで光らせることができます。

デフォルトでは受け付けないようになっているので、KORG 公式アプリの "KONTROL Editor" を使って LED モードを変える必要があります。

KONTROL Editor 設定

左上のブロックをクリックし、LED Mode を "External" にして、転送 -> シーン・データを書き込み します。

ただし、External モードでは SW を押したときに光らなくなります。

"KONTROL Editor" を使えば、割り当てメッセージを変えたり、ボタンの動作を、押す度に ON/OFF が切り替わるトグル動作に変更したりすることもできます。

ボタン動作設定


Exclusive Message
を使えば MATLAB からも変えられそうな気がするのですが、うまくいきませんでした。(゚~゚)

やり方をご存じの方がいらっしゃったらぜひ教えてください。<(_ _)>


発光パターンは ChatGPT さんに適当に作ってもらいました。

Live Editor で実行します。

availableDevices = mididevinfo;

outputMIDIDevices = {availableDevices.output.Name};
idx = contains(outputMIDIDevices,'nanoKONTROL2');
outputDeviceName = outputMIDIDevices{idx};
outputDevice = mididevice(outputDeviceName);

MIDIcontrols = [32:39 48:55 64:71];
ON = 127;
OFF = 0;


% パターン1: ランダムに光る
for n = 1:3
    randomOrder = MIDIcontrols(randperm(length(MIDIcontrols)));
    for c = randomOrder
        midisend(outputDevice, 'ControlChange', 1, c, ON);
        pause(0.1)
        midisend(outputDevice, 'ControlChange', 1, c, OFF);
    end
end

% パターン2: 全部点灯 -> 全部消灯
for n = 1:2
    for c = MIDIcontrols
        midisend(outputDevice, 'ControlChange', 1, c, ON);
    end
    pause(0.5)
    for c = MIDIcontrols
        midisend(outputDevice, 'ControlChange', 1, c, OFF);
    end
    pause(0.5)
end

% パターン3: 逆順に光る
for n = 1:2
    for c = fliplr(MIDIcontrols)
        midisend(outputDevice, 'ControlChange', 1, c, ON);
        pause(0.1)
        midisend(outputDevice, 'ControlChange', 1, c, OFF);
    end
end

% パターン4: 交互に光る
for n = 1:2
    for c = 1:2:length(MIDIcontrols)
        midisend(outputDevice, 'ControlChange', 1, MIDIcontrols(c), ON);
    end
    pause(0.5)
    for c = 1:2:length(MIDIcontrols)
        midisend(outputDevice, 'ControlChange', 1, MIDIcontrols(c), OFF);
    end
    pause(0.5)
    for c = 2:2:length(MIDIcontrols)
        midisend(outputDevice, 'ControlChange', 1, MIDIcontrols(c), ON);
    end
    pause(0.5)
    for c = 2:2:length(MIDIcontrols)
        midisend(outputDevice, 'ControlChange', 1, MIDIcontrols(c), OFF);
    end
    pause(0.5)
end

% パターン5: 半分に分割
half = floor(length(MIDIcontrols)/2);
left = MIDIcontrols(1:half);
right = MIDIcontrols(end-half+1:end);

for n = 1:3
    for i = 1:half
        midisend(outputDevice, 'ControlChange', 1, left(i), ON);
        midisend(outputDevice, 'ControlChange', 1, right(i), ON);
        pause(0.1)
        midisend(outputDevice, 'ControlChange', 1, left(i), OFF);
        midisend(outputDevice, 'ControlChange', 1, right(i), OFF);
    end
end

% パターン6: 上下分割ループ
len = length(MIDIcontrols);
left = MIDIcontrols(1:len*2/3);
right = MIDIcontrols(end:-1:len/3+1);

for n = 1:4
    for i = 1:len*2/3
        midisend(outputDevice, 'ControlChange', 1, left(i), ON);
        midisend(outputDevice, 'ControlChange', 1, right(i), ON);
        pause(0.1)
        midisend(outputDevice, 'ControlChange', 1, left(i), OFF);
        midisend(outputDevice, 'ControlChange', 1, right(i), OFF);
    end
end

以下は、最後がちょっと違いますが実行例です。(音なし)

実行例


あとがき


いかがでしたでしょうか?

特に MIDI の知識がなくても、簡単に使えるかとは思います。

やはり、フィジカル・コントローラーは楽しいですね。 (。・_・。)ノ

いいなと思ったら応援しよう!