64パッドの指位置図示とノート変換のためのJSFXを作った

## 動機

- ブラウザで動く[64パッドのレイアウト図示用MIDIモニタを作った](https://qiita.com/ucosarv/items/d642a70c19a57daf6945)
  - 主に受け取ったコード演奏を図示する
  - 一つのMIDIノート(音高)に複数のパッドが対応するので、実際に何を押しているのか分かりにくい
  - 押した位置を図時するためのモードも作ったが不便
    - ブラウザとDAWを切り替えたりデスクトップ録画するのが面倒
    - 音を出せるようにしたいがMIDIのルーティングや遅延を考えるとブラウザではやりたくない
- DAW内で動くものを作りたい
  - ReaperのJSFX(EEL2)、`Effects/midi/MIDI Transpose Notes`あたりを見てたらできそうな気がしてきた
  - ReaperのJSFXをVSTとして別のDAWで動かすソフトが存在する(YSFX)
    - 他のDAWでも動かせそう 


## 作ったもの

- 64 Pad Software Note Mode JSFX
  - Ableton LiveのDrumrack相当のMIDI入力を、押した位置の図示しながら音高を変換する
  - 自分で使いたいレイアウトとP4thに対応
- Mm3rdRotatedLayout
  ![Mm3rdRotatedLayout](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/c6ee6c1d-53de-26e6-0755-6bc0922687ba.png)
- P4thLayout(完全四度クロマチック、4ths String Layout)
  ![P4thLayout](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/8c8f6b0c-5110-0ea9-d173-9cce83be89aa.png)
- コードは長くなったので記事末尾参照




## 動かしてみた

- 注意：素人が作ったプラグインなので何かあっても責任は負えません


### 動作例：Reaper
- JSFX/VSTを並べて配置するだけ
![Reaper Setting](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/395909fb-cdd7-533b-5bd8-a9426348df73.png)
- コード演奏(Mm3rdRotatedLayout)
![Reaper Chord Play Sample](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/9d7c5269-2ac2-f7f8-30e8-48eb2684c887.gif)


### 動作例：Ableton Live + ysfx-s instrument
- VSTi・VST(FX)の後にVSTiを置けないので、2トラック使ってルーティングする
- ![スクリーンショット 2024-10-13 125107_書き込み.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/ebf773a6-d7b7-c83e-dc74-4847f0eb204a.png)
- コード演奏(Mm3rdRotatedLayout)
![AbletonLive Code Play Sample](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/1c9ab999-9f2b-09be-ce29-c77d5b989e7d.gif)
- ドラムラック演奏の可視化(MonitorOnly)![Ableton Live Drumrack Play Sample](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/46d86dfb-7225-69e7-6cda-8e6c7d843a16.gif)


### 動作例：Dorico + Modalics Plugin Buddy + ysfx-s instrument
- Doricoは譜面ソフトなのでMIDIのルーティングはほとんどできない
- Modalics Plugin Buddy(VSTをロードできるVST)でysfx-sをMIDI FXスロットに入れ、Instrumentスロットに演奏用のVSTを入れて使用している

![Dorico Setting Sample](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/cc64ccf6-c6d4-0e5e-f8b9-495d4ef11ece.png)

### 動作例：Bitwig 8-Track + ysfx-s instrument
- 順番に並べるだけ(P4th Layout)
- ![Bitwig Setting Sample](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/c7e84911-0655-892a-b389-0e523aa5a28e.png)


## この記事のJSFXのプラグインを動かすには

- Reaper
  - プラグイン(VST/JS等)の選択画面でメニューのエフェクト→新規JSエフェクトを作成、で空のJSFXを作れるのでそこに貼り付ける
  - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/981899e2-c6d1-ba31-a14e-0cd930abf0f9.png)

- Reaper以外
  - [YSFXのフォーク](https://github.com/JoepVanlier/ysfx/releases)（※[ReadMe](https://github.com/JoepVanlier/ysfx)）を使用する(VST/CLAPプラグイン)
  - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/c8ea8340-9dd8-e2d1-c292-7a0c739d57b2.png)
  - この記事では v0.0.26 を使用(頻繁に更新されているようなので注意)
  - 任意の場所・ファイル名でJSFXを保存しておき、YSFSにLoadして使用する


  - DAW側でMIDIルーティングができない場合は、Modalics Plugin BuddyなどのVSTコンテナホストを使うのが簡単
- 音高変換後のMIDI Noteの記録は各DAWのMIDI Burn機能か、BirdBird MIDICapを使うと便利



## 余談：BirdBird MIDICapはとてもおすすめ(レトロスペクティブMIDIキャプチャ)

- https://www.birdsthings.com/midi-cap
- レトロスペクティブMIDIキャプチャ機能を提供するVSTi/CLAPプラグイン
  - 直近60秒のMIDI入力から好きな部分を切り出して保存できる
  - 好きなDAWの好きな場所に置けるのでものすごくお勧め
    - アルペジエーター等MIDI-FXの出力も直感的にキャプチャできる
- 操作方法
  - ![MIDI Cap](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282700/f565b6b3-cb8e-f277-bc9f-e7e2b383624e.png)
  - 保存したいところを選択し(上の画像の背景白)、DAWやエクスプローラーにドラッグドロップで保存する
  - マウスホイールを押しながらドラッグでスクロール
    - テンポを合わせてくれないので、DAWと同じか1/2のテンポで弾くと後が楽になる
  - Ctrl+クリックでキャプチャ停止・再開
  - Shift+Rでリセット
- Lite版(無償)と有償版の違いは記録できる時間の長さだけ



## 作成したJSFXのコード


```64PadSwNoteMode.eel2
desc:64Pad Software Note Mode
//tags: MIDI processing


in_pin:none
out_pin:none
options:gfx_hz=60

slider1:layout=0<0, 5, 1{Mm3rdRotated,P5thRotated,P5th,P4th,MonitorOnly}>Layout
slider2:octave=0<-3, 3, 1>Octave
slider3:transpose=0<-12, 12, 1>Transpose
slider4:text_alpha=0.9<0.0, 1.0, 0.1>TextAlpha



@init

RECT_SIZE = 50;
PAD_COUNT = 64;
PAD_COLS = 8;
NOTE_NUM_MAX = 127;

notebuf = 0; // 4 entries per open note: orignote, channel, vel, transnote
buflen = 0;

// Midi Note Numberをパッドの位置として、pad_*_arrayインデックスに変換するための配列
n2p = 400; idx = 0; loop(NOTE_NUM_MAX, (n2p[idx]=0; idx+=1;));
n2p[64]=0;  n2p[65]=1;  n2p[66]=2;  n2p[67]=3;  n2p[96]=4;  n2p[97]=5;  n2p[98]=6;  n2p[99]=7;
n2p[60]=8;  n2p[61]=9;  n2p[62]=10;  n2p[63]=11;  n2p[92]=12;  n2p[93]=13;  n2p[94]=14;  n2p[95]=15;
n2p[56]=16;  n2p[57]=17;  n2p[58]=18;  n2p[59]=19;  n2p[88]=20;  n2p[89]=21;  n2p[90]=22;  n2p[91]=23;
n2p[52]=24;  n2p[53]=25;  n2p[54]=26;  n2p[55]=27;  n2p[84]=28;  n2p[85]=29;  n2p[86]=30;  n2p[87]=31;
n2p[48]=32;  n2p[49]=33;  n2p[50]=34;  n2p[51]=35;  n2p[80]=36;  n2p[81]=37;  n2p[82]=38;  n2p[83]=39;
n2p[44]=40;  n2p[45]=41;  n2p[46]=42;  n2p[47]=43;  n2p[76]=44;  n2p[77]=45;  n2p[78]=46;  n2p[79]=47;
n2p[40]=48;  n2p[41]=49;  n2p[42]=50;  n2p[43]=51;  n2p[72]=52;  n2p[73]=53;  n2p[74]=54;  n2p[75]=55;
n2p[36]=56;  n2p[37]=57;  n2p[38]=58;  n2p[39]=59;  n2p[68]=60;  n2p[69]=61;  n2p[70]=62;  n2p[71]=63;

note2name = 700;
note2name[0] = "C"; note2name[1] = "C#"; note2name[2] = "D"; note2name[3] = "D#";
note2name[4] = "E"; note2name[5] = "F"; note2name[6] = "F#"; note2name[7] = "G";
note2name[8] = "G#"; note2name[9] = "A"; note2name[10] = "A#"; note2name[11] = "B";
function get_note_name(note_num) (
    note2name[(note_num % 12)|0];
);
function get_note_name_diatonic(note_num) (
    num = (note_num % 12)|0;
    (num == 1) ? ""
        : num == 3 ? "" 
        : num == 6 ? "" 
        : num == 8 ? "" 
        : num == 10 ? "" 
        : note2name[num];
);
function set_note_name_array(n2t, n2n) (
    idx = 36; loop(PAD_COUNT,
        note = n2t[idx];
        pos  = n2p[idx];
        n2n[pos] = get_note_name(note);
        idx+=1;);
);
function set_note_name_array_note_num(n2t, n2n) (
    idx = 36; loop(PAD_COUNT,
        note = n2t[idx];
        pos  = n2p[idx];
        n2n[pos] = note;
        idx+=1;);
);
function apply_offset(n2t, offset) (
    idx = 36; loop(PAD_COUNT, (n2t[idx] += offset; idx += 1;));
);

function set_layout_Mm3rdRotated(n2t, n2n, offset) (
    idx = 0; loop(NOTE_NUM_MAX, (n2t[idx]=0; idx+=1;));
    n2t[64]=43;  n2t[65]=47;  n2t[66]=50;  n2t[67]=54;  n2t[96]=57;  n2t[97]=61;  n2t[98]=64;  n2t[99]=68;
    n2t[60]=42;  n2t[61]=46;  n2t[62]=49;  n2t[63]=53;  n2t[92]=56;  n2t[93]=60;  n2t[94]=63;  n2t[95]=67;
    n2t[56]=41;  n2t[57]=45;  n2t[58]=48;  n2t[59]=52;  n2t[88]=55;  n2t[89]=59;  n2t[90]=62;  n2t[91]=66;
    n2t[52]=40;  n2t[53]=44;  n2t[54]=47;  n2t[55]=51;  n2t[84]=54;  n2t[85]=58;  n2t[86]=61;  n2t[87]=65;
    n2t[48]=39;  n2t[49]=43;  n2t[50]=46;  n2t[51]=50;  n2t[80]=53;  n2t[81]=57;  n2t[82]=60;  n2t[83]=64;
    n2t[44]=38;  n2t[45]=42;  n2t[46]=45;  n2t[47]=49;  n2t[76]=52;  n2t[77]=56;  n2t[78]=59;  n2t[79]=63;
    n2t[40]=37;  n2t[41]=41;  n2t[42]=44;  n2t[43]=48;  n2t[72]=51;  n2t[73]=55;  n2t[74]=58;  n2t[75]=62;
    n2t[36]=36;  n2t[37]=40;  n2t[38]=43;  n2t[39]=47;  n2t[68]=50;  n2t[69]=54;  n2t[70]=57;  n2t[71]=61;
    apply_offset(n2t, offset);
    set_note_name_array(n2t, n2n);
    n2n[0]=1?n2n[0]:"";  n2n[1]=1?n2n[1]:"";  n2n[2]=1?n2n[2]:"";  n2n[3]=0?n2n[3]:"";  n2n[4]=1?n2n[4]:"";  n2n[5]=0?n2n[5]:"";  n2n[6]=1?n2n[6]:"";  n2n[7]=0?n2n[7]:"";
    n2n[8]=0?n2n[8]:"";  n2n[9]=0?n2n[9]:"";  n2n[10]=0?n2n[10]:"";  n2n[11]=1?n2n[11]:"";  n2n[12]=0?n2n[12]:"";  n2n[13]=1?n2n[13]:"";  n2n[14]=0?n2n[14]:"";  n2n[15]=1?n2n[15]:"";
    n2n[16]=1?n2n[16]:"";  n2n[17]=1?n2n[17]:"";  n2n[18]=1?n2n[18]:"";  n2n[19]=1?n2n[19]:"";  n2n[20]=1?n2n[20]:"";  n2n[21]=1?n2n[21]:"";  n2n[22]=1?n2n[22]:"";  n2n[23]=0?n2n[23]:"";
    n2n[24]=1?n2n[24]:"";  n2n[25]=0?n2n[25]:"";  n2n[26]=1?n2n[26]:"";  n2n[27]=0?n2n[27]:"";  n2n[28]=0?n2n[28]:"";  n2n[29]=0?n2n[29]:"";  n2n[30]=0?n2n[30]:"";  n2n[31]=1?n2n[31]:"";
    n2n[32]=0?n2n[32]:"";  n2n[33]=1?n2n[33]:"";  n2n[34]=0?n2n[34]:"";  n2n[35]=1?n2n[35]:"";  n2n[36]=1?n2n[36]:"";  n2n[37]=1?n2n[37]:"";  n2n[38]=1?n2n[38]:"";  n2n[39]=1?n2n[39]:"";
    n2n[40]=1?n2n[40]:"";  n2n[41]=0?n2n[41]:"";  n2n[42]=1?n2n[42]:"";  n2n[43]=0?n2n[43]:"";  n2n[44]=1?n2n[44]:"";  n2n[45]=0?n2n[45]:"";  n2n[46]=1?n2n[46]:"";  n2n[47]=0?n2n[47]:"";
    n2n[48]=0?n2n[48]:"";  n2n[49]=1?n2n[49]:"";  n2n[50]=0?n2n[50]:"";  n2n[51]=1?n2n[51]:"";  n2n[52]=0?n2n[52]:"";  n2n[53]=1?n2n[53]:"";  n2n[54]=0?n2n[54]:"";  n2n[55]=1?n2n[55]:"";
    n2n[56]=1?n2n[56]:"";  n2n[57]=1?n2n[57]:"";  n2n[58]=1?n2n[58]:"";  n2n[59]=1?n2n[59]:"";  n2n[60]=1?n2n[60]:"";  n2n[61]=0?n2n[61]:"";  n2n[62]=1?n2n[62]:"";  n2n[63]=0?n2n[63]:"";
);
function set_layout_P4th(n2t, n2n, offset) (
    idx = 0; loop(NOTE_NUM_MAX, (n2t[idx]=0; idx+=1;));
    n2t[64]=71;  n2t[65]=72;  n2t[66]=73;  n2t[67]=74;  n2t[96]=75;  n2t[97]=76;  n2t[98]=77;  n2t[99]=78;
    n2t[60]=66;  n2t[61]=67;  n2t[62]=68;  n2t[63]=69;  n2t[92]=70;  n2t[93]=71;  n2t[94]=72;  n2t[95]=73;
    n2t[56]=61;  n2t[57]=62;  n2t[58]=63;  n2t[59]=64;  n2t[88]=65;  n2t[89]=66;  n2t[90]=67;  n2t[91]=68;
    n2t[52]=56;  n2t[53]=57;  n2t[54]=58;  n2t[55]=59;  n2t[84]=60;  n2t[85]=61;  n2t[86]=62;  n2t[87]=63;
    n2t[48]=51;  n2t[49]=52;  n2t[50]=53;  n2t[51]=54;  n2t[80]=55;  n2t[81]=56;  n2t[82]=57;  n2t[83]=58;
    n2t[44]=46;  n2t[45]=47;  n2t[46]=48;  n2t[47]=49;  n2t[76]=50;  n2t[77]=51;  n2t[78]=52;  n2t[79]=53;
    n2t[40]=41;  n2t[41]=42;  n2t[42]=43;  n2t[43]=44;  n2t[72]=45;  n2t[73]=46;  n2t[74]=47;  n2t[75]=48;
    n2t[36]=36;  n2t[37]=37;  n2t[38]=38;  n2t[39]=39;  n2t[68]=40;  n2t[69]=41;  n2t[70]=42;  n2t[71]=43;
    apply_offset(n2t, offset);
    set_note_name_array(n2t, n2n);
    n2n[0]=1?n2n[0]:"";  n2n[1]=1?n2n[1]:"";  n2n[2]=0?n2n[2]:"";  n2n[3]=1?n2n[3]:"";  n2n[4]=0?n2n[4]:"";  n2n[5]=1?n2n[5]:"";  n2n[6]=1?n2n[6]:"";  n2n[7]=0?n2n[7]:"";  
    n2n[8]=0?n2n[8]:"";  n2n[9]=1?n2n[9]:"";  n2n[10]=0?n2n[10]:"";  n2n[11]=1?n2n[11]:"";  n2n[12]=0?n2n[12]:"";  n2n[13]=1?n2n[13]:"";  n2n[14]=1?n2n[14]:"";  n2n[15]=0?n2n[15]:"";  
    n2n[16]=0?n2n[16]:"";  n2n[17]=1?n2n[17]:"";  n2n[18]=0?n2n[18]:"";  n2n[19]=1?n2n[19]:"";  n2n[20]=1?n2n[20]:"";  n2n[21]=0?n2n[21]:"";  n2n[22]=1?n2n[22]:"";  n2n[23]=0?n2n[23]:"";  
    n2n[24]=0?n2n[24]:"";  n2n[25]=1?n2n[25]:"";  n2n[26]=0?n2n[26]:"";  n2n[27]=1?n2n[27]:"";  n2n[28]=1?n2n[28]:"";  n2n[29]=0?n2n[29]:"";  n2n[30]=1?n2n[30]:"";  n2n[31]=0?n2n[31]:"";  
    n2n[32]=0?n2n[32]:"";  n2n[33]=1?n2n[33]:"";  n2n[34]=1?n2n[34]:"";  n2n[35]=0?n2n[35]:"";  n2n[36]=1?n2n[36]:"";  n2n[37]=0?n2n[37]:"";  n2n[38]=1?n2n[38]:"";  n2n[39]=0?n2n[39]:"";  
    n2n[40]=0?n2n[40]:"";  n2n[41]=1?n2n[41]:"";  n2n[42]=1?n2n[42]:"";  n2n[43]=0?n2n[43]:"";  n2n[44]=1?n2n[44]:"";  n2n[45]=0?n2n[45]:"";  n2n[46]=1?n2n[46]:"";  n2n[47]=1?n2n[47]:"";  
    n2n[48]=1?n2n[48]:"";  n2n[49]=0?n2n[49]:"";  n2n[50]=1?n2n[50]:"";  n2n[51]=0?n2n[51]:"";  n2n[52]=1?n2n[52]:"";  n2n[53]=0?n2n[53]:"";  n2n[54]=1?n2n[54]:"";  n2n[55]=1?n2n[55]:"";  
    n2n[56]=1?n2n[56]:"";  n2n[57]=0?n2n[57]:"";  n2n[58]=1?n2n[58]:"";  n2n[59]=0?n2n[59]:"";  n2n[60]=1?n2n[60]:"";  n2n[61]=1?n2n[61]:"";  n2n[62]=0?n2n[62]:"";  n2n[63]=1?n2n[63]:"";  
);
function set_layout_P5th(n2t, n2n, offset) (
    idx = 0; loop(NOTE_NUM_MAX, (n2t[idx]=0; idx+=1;));
    n2t[64]=85; n2t[65]=86; n2t[66]=87; n2t[67]=88; n2t[96]=89; n2t[97]=90; n2t[98]=91; n2t[99]=92;
    n2t[60]=78; n2t[61]=79; n2t[62]=80; n2t[63]=81; n2t[92]=82; n2t[93]=83; n2t[94]=84; n2t[95]=85;
    n2t[56]=71; n2t[57]=72; n2t[58]=73; n2t[59]=74; n2t[88]=75; n2t[89]=76; n2t[90]=77; n2t[91]=78;
    n2t[52]=64; n2t[53]=65; n2t[54]=66; n2t[55]=67; n2t[84]=68; n2t[85]=69; n2t[86]=70; n2t[87]=71;
    n2t[48]=57; n2t[49]=58; n2t[50]=59; n2t[51]=60; n2t[80]=61; n2t[81]=62; n2t[82]=63; n2t[83]=64;
    n2t[44]=50; n2t[45]=51; n2t[46]=52; n2t[47]=53; n2t[76]=54; n2t[77]=55; n2t[78]=56; n2t[79]=57;
    n2t[40]=43; n2t[41]=44; n2t[42]=45; n2t[43]=46; n2t[72]=47; n2t[73]=48; n2t[74]=49; n2t[75]=50;
    n2t[36]=36; n2t[37]=37; n2t[38]=38; n2t[39]=39; n2t[68]=40; n2t[69]=41; n2t[70]=42; n2t[71]=43;
    apply_offset(n2t, offset);
    set_note_name_array(n2t, n2n);
    n2n[0]=0?n2n[0]:"";  n2n[1]=1?n2n[1]:"";  n2n[2]=0?n2n[2]:"";  n2n[3]=1?n2n[3]:"";  n2n[4]=1?n2n[4]:"";  n2n[5]=0?n2n[5]:"";  n2n[6]=1?n2n[6]:"";  n2n[7]=0?n2n[7]:"";  
    n2n[8]=0?n2n[8]:"";  n2n[9]=1?n2n[9]:"";  n2n[10]=0?n2n[10]:"";  n2n[11]=1?n2n[11]:"";  n2n[12]=0?n2n[12]:"";  n2n[13]=1?n2n[13]:"";  n2n[14]=1?n2n[14]:"";  n2n[15]=0?n2n[15]:"";  
    n2n[16]=1?n2n[16]:"";  n2n[17]=1?n2n[17]:"";  n2n[18]=0?n2n[18]:"";  n2n[19]=1?n2n[19]:"";  n2n[20]=0?n2n[20]:"";  n2n[21]=1?n2n[21]:"";  n2n[22]=1?n2n[22]:"";  n2n[23]=0?n2n[23]:"";  
    n2n[24]=1?n2n[24]:"";  n2n[25]=1?n2n[25]:"";  n2n[26]=0?n2n[26]:"";  n2n[27]=1?n2n[27]:"";  n2n[28]=0?n2n[28]:"";  n2n[29]=1?n2n[29]:"";  n2n[30]=0?n2n[30]:"";  n2n[31]=1?n2n[31]:"";  
    n2n[32]=1?n2n[32]:"";  n2n[33]=0?n2n[33]:"";  n2n[34]=1?n2n[34]:"";  n2n[35]=1?n2n[35]:"";  n2n[36]=0?n2n[36]:"";  n2n[37]=1?n2n[37]:"";  n2n[38]=0?n2n[38]:"";  n2n[39]=1?n2n[39]:"";  
    n2n[40]=1?n2n[40]:"";  n2n[41]=0?n2n[41]:"";  n2n[42]=1?n2n[42]:"";  n2n[43]=1?n2n[43]:"";  n2n[44]=0?n2n[44]:"";  n2n[45]=1?n2n[45]:"";  n2n[46]=0?n2n[46]:"";  n2n[47]=1?n2n[47]:"";  
    n2n[48]=1?n2n[48]:"";  n2n[49]=0?n2n[49]:"";  n2n[50]=1?n2n[50]:"";  n2n[51]=0?n2n[51]:"";  n2n[52]=1?n2n[52]:"";  n2n[53]=1?n2n[53]:"";  n2n[54]=0?n2n[54]:"";  n2n[55]=1?n2n[55]:"";  
    n2n[56]=1?n2n[56]:"";  n2n[57]=0?n2n[57]:"";  n2n[58]=1?n2n[58]:"";  n2n[59]=0?n2n[59]:"";  n2n[60]=1?n2n[60]:"";  n2n[61]=1?n2n[61]:"";  n2n[62]=0?n2n[62]:"";  n2n[63]=1?n2n[63]:"";  
);
function set_layout_P5thRotated(n2t, n2n, offset) (
    idx = 0; loop(NOTE_NUM_MAX, (n2t[idx]=0; idx+=1;));
    n2t[64]=43;  n2t[65]=50;  n2t[66]=57;  n2t[67]=64;  n2t[96]=71;  n2t[97]=78;  n2t[98]=85;  n2t[99]=92;
    n2t[60]=42;  n2t[61]=49;  n2t[62]=56;  n2t[63]=63;  n2t[92]=70;  n2t[93]=77;  n2t[94]=84;  n2t[95]=91;
    n2t[56]=41;  n2t[57]=48;  n2t[58]=55;  n2t[59]=62;  n2t[88]=69;  n2t[89]=76;  n2t[90]=83;  n2t[91]=90;
    n2t[52]=40;  n2t[53]=47;  n2t[54]=54;  n2t[55]=61;  n2t[84]=68;  n2t[85]=75;  n2t[86]=82;  n2t[87]=89;
    n2t[48]=39;  n2t[49]=46;  n2t[50]=53;  n2t[51]=60;  n2t[80]=67;  n2t[81]=74;  n2t[82]=81;  n2t[83]=88;
    n2t[44]=38;  n2t[45]=45;  n2t[46]=52;  n2t[47]=59;  n2t[76]=66;  n2t[77]=73;  n2t[78]=80;  n2t[79]=87;
    n2t[40]=37;  n2t[41]=44;  n2t[42]=51;  n2t[43]=58;  n2t[72]=65;  n2t[73]=72;  n2t[74]=79;  n2t[75]=86;
    n2t[36]=36;  n2t[37]=43;  n2t[38]=50;  n2t[39]=57;  n2t[68]=64;  n2t[69]=71;  n2t[70]=78;  n2t[71]=85;
    apply_offset(n2t, offset);
    set_note_name_array(n2t, n2n);
    n2n[0]=1?n2n[0]:"";  n2n[1]=1?n2n[1]:"";  n2n[2]=1?n2n[2]:"";  n2n[3]=1?n2n[3]:"";  n2n[4]=1?n2n[4]:"";  n2n[5]=0?n2n[5]:"";  n2n[6]=0?n2n[6]:"";  n2n[7]=0?n2n[7]:"";  
    n2n[8]=0?n2n[8]:"";  n2n[9]=0?n2n[9]:"";  n2n[10]=0?n2n[10]:"";  n2n[11]=0?n2n[11]:"";  n2n[12]=0?n2n[12]:"";  n2n[13]=1?n2n[13]:"";  n2n[14]=1?n2n[14]:"";  n2n[15]=1?n2n[15]:"";  
    n2n[16]=1?n2n[16]:"";  n2n[17]=1?n2n[17]:"";  n2n[18]=1?n2n[18]:"";  n2n[19]=1?n2n[19]:"";  n2n[20]=1?n2n[20]:"";  n2n[21]=1?n2n[21]:"";  n2n[22]=1?n2n[22]:"";  n2n[23]=0?n2n[23]:"";  
    n2n[24]=1?n2n[24]:"";  n2n[25]=1?n2n[25]:"";  n2n[26]=0?n2n[26]:"";  n2n[27]=0?n2n[27]:"";  n2n[28]=0?n2n[28]:"";  n2n[29]=0?n2n[29]:"";  n2n[30]=0?n2n[30]:"";  n2n[31]=1?n2n[31]:"";  
    n2n[32]=0?n2n[32]:"";  n2n[33]=0?n2n[33]:"";  n2n[34]=1?n2n[34]:"";  n2n[35]=1?n2n[35]:"";  n2n[36]=1?n2n[36]:"";  n2n[37]=1?n2n[37]:"";  n2n[38]=1?n2n[38]:"";  n2n[39]=1?n2n[39]:"";  
    n2n[40]=1?n2n[40]:"";  n2n[41]=1?n2n[41]:"";  n2n[42]=1?n2n[42]:"";  n2n[43]=1?n2n[43]:"";  n2n[44]=0?n2n[44]:"";  n2n[45]=0?n2n[45]:"";  n2n[46]=0?n2n[46]:"";  n2n[47]=0?n2n[47]:"";  
    n2n[48]=0?n2n[48]:"";  n2n[49]=0?n2n[49]:"";  n2n[50]=0?n2n[50]:"";  n2n[51]=0?n2n[51]:"";  n2n[52]=1?n2n[52]:"";  n2n[53]=1?n2n[53]:"";  n2n[54]=1?n2n[54]:"";  n2n[55]=1?n2n[55]:"";  
    n2n[56]=1?n2n[56]:"";  n2n[57]=1?n2n[57]:"";  n2n[58]=1?n2n[58]:"";  n2n[59]=1?n2n[59]:"";  n2n[60]=1?n2n[60]:"";  n2n[61]=1?n2n[61]:"";  n2n[62]=0?n2n[62]:"";  n2n[63]=0?n2n[63]:"";  
);
function set_layout_nop(n2t, n2n, offset) (
    idx = 0; loop(NOTE_NUM_MAX, (n2t[idx]=0; idx+=1;));
    n2t[64]=64;  n2t[65]=65;  n2t[66]=66;  n2t[67]=67;  n2t[96]=96;  n2t[97]=97;  n2t[98]=98;  n2t[99]=99;
    n2t[60]=60;  n2t[61]=61;  n2t[62]=62;  n2t[63]=63;  n2t[92]=92;  n2t[93]=93;  n2t[94]=94;  n2t[95]=95;
    n2t[56]=56;  n2t[57]=57;  n2t[58]=58;  n2t[59]=59;  n2t[88]=88;  n2t[89]=89;  n2t[90]=90;  n2t[91]=91;
    n2t[52]=52;  n2t[53]=53;  n2t[54]=54;  n2t[55]=55;  n2t[84]=84;  n2t[85]=85;  n2t[86]=86;  n2t[87]=87;
    n2t[48]=48;  n2t[49]=49;  n2t[50]=50;  n2t[51]=51;  n2t[80]=80;  n2t[81]=81;  n2t[82]=82;  n2t[83]=83;
    n2t[44]=44;  n2t[45]=45;  n2t[46]=46;  n2t[47]=47;  n2t[76]=76;  n2t[77]=77;  n2t[78]=78;  n2t[79]=79;
    n2t[40]=40;  n2t[41]=41;  n2t[42]=42;  n2t[43]=43;  n2t[72]=72;  n2t[73]=73;  n2t[74]=74;  n2t[75]=75;
    n2t[36]=36;  n2t[37]=37;  n2t[38]=38;  n2t[39]=39;  n2t[68]=68;  n2t[69]=69;  n2t[70]=70;  n2t[71]=71;
    apply_offset(n2t, offset);
    set_note_name_array(n2t, n2n);
);
function select_layout(layout_num, n2t, n2n, offset) (
    layout_num == 0 ? set_layout_Mm3rdRotated(n2t, n2n, offset)
        : layout_num == 1 ? set_layout_P5thRotated(n2t, n2n, offset)
        : layout_num == 2 ? set_layout_P5th(n2t, n2n, offset)
        : layout_num == 3 ? set_layout_P4th(n2t, n2n, offset)
        : layout_num == 4 ? set_layout_nop(n2t, n2n, offset)
        : set_layout_nop(n2t, n2n, offset);
);
function calc_offset() (
    transpose + octave * 12;
);


// 64パッドの各パッドの状態を管理するための配列 インデックス0は左上 値は変換後の音程(値0はオフ)
pad_state_array = 330; idx = 0; loop(PAD_COUNT, (pad_state_array[idx]=0; idx+=1;));
pad_name_array  = 530; // idx = 0; loop(PAD_COUNT, (pad_name_array[idx]=sprintf("%i", idx); idx+=1;));
// Midi Note Numberを新しいNote Numberに変換するための配列
n2t = 200;
select_layout(layout, n2t, pad_name_array, calc_offset());

function set_pad_state(note_num, val) (
    pad_state_array[n2p[note_num]] = val;
);


// color setting
function set_color_panel_off() (
    gfx_r = 0.17; gfx_g = 0.17; gfx_b = 0.25;
);
function set_color_panel_on() (
    gfx_r = 0.92; gfx_g = 0.88; gfx_b = 0.08;
);
function set_color_label_on() (
    set_color_panel_off();
);
function set_color_label_off() (
    // gfx_r = 0.41; gfx_g = 0.4; gfx_b = 0.03;
    gfx_r = 0.31; gfx_g = 0.3; gfx_b = 0.02;
);
function set_color_panel_zero() (
    gfx_r = 0; gfx_g = 0; gfx_b = 0;
);



// slider
octave_last = octave;
transpose_last = transpose;
layout_last = layout;

@slider

octave_last != octave ? (
    select_layout(layout, n2t, pad_name_array, calc_offset());
    octave_last = octave;
);
transpose_last != transpose ? (
    select_layout(layout, n2t, pad_name_array, calc_offset());
    transpose_last = transpose;
);
layout_last != layout ? (
    layout_last = layout;
    select_layout(layout, n2t, pad_name_array, calc_offset());
);

@block


while 
(
  midirecv(offs, m1, m2) ?
  (
    s = m1&$xF0;
    s == $x90 || s == $x80 ? // note-on or note-off
    (
        n = m2&$xFF; // original note
        c = m1&$xF; // channel
        v = (m2&$xFF00)/256; // velocity
        t = n2t[n]; // transposed note
        m2 = m2+t-n; // apply transposition      

        i = -1;
        while // look for this note|channel already in the buffer
        (
          i = i+1;
          i < buflen && (notebuf[4*i]|0 != n || notebuf[4*i+1]|0 != c);
        );

        (s == $x90 && v > 0) ? // note-on, add to buffer
        (
          set_pad_state(n, t); // pad_state_array[n2p[n]] = t;

          notebuf[4*i] = n;
          notebuf[4*i+1] = c;
          notebuf[4*i+2] = v;
          notebuf[4*i+3] = t;
          i == buflen ? buflen = buflen+1;
        ) 
        : // note-off, remove from buffer
        (
          set_pad_state(n, 0); // pad_state_array[n2p[n]] = 0;

          i < buflen ?
          (
            memcpy(notebuf+4*i, notebuf+4*(i+1), 4*(buflen-i-1));  // delete the entry
            buflen = buflen-1;
          );
        );
    );
    midisend(offs, m1, m2);
  );
);


@gfx 400 400

gfx_x = 0;
gfx_y = 0;

// clear canvas
gfx_w = PAD_COLS*RECT_SIZE; gfx_h = gfx_w;
set_color_panel_off();
gfx_rect(0, 0, PAD_COLS*RECT_SIZE, PAD_COLS*RECT_SIZE);

function draw_pad_label(label)(
    gfx_setfont(1, "Arial", 36);
    tmp_x = gfx_x; gfx_x += 4;
    tmp_a = gfx_a; gfx_a = text_alpha;
    gfx_drawstr(label, 8, gfx_x+RECT_SIZE, gfx_y+RECT_SIZE);
    gfx_a = tmp_a;
    gfx_x = tmp_x;
);
function draw_pad_label_num(num)(
    gfx_setfont(1, "Arial", 24);
    tmp_x = gfx_x; gfx_x += 4;
    tmp_a = gfx_a; gfx_a = text_alpha;
    gfx_printf("%i", num);
    gfx_a = tmp_a;
    gfx_x = tmp_x;
);
// draw note-on pad
idx = 0; loop(64, 
    next_x = gfx_x + RECT_SIZE;
    label = pad_name_array[idx];
    (pad_state_array[idx]>0) ? (
        // rect
        set_color_panel_on();
        gfx_rect(gfx_x+1, gfx_y+1, RECT_SIZE-1, RECT_SIZE-1);
        // label text color
        set_color_label_on();
        // draw_pad_label(label);
        draw_pad_label(get_note_name(pad_state_array[idx]));
    ) : (
        // label text color
        set_color_label_off();
        draw_pad_label(label);
    );
    // draw_pad_label(get_note_name(pad_state_array[idx]));
    // draw_pad_label_num(label);
    gfx_x = next_x;
    (PAD_COLS-1) == (idx % PAD_COLS) ? (
        gfx_x = 0;
        gfx_y += RECT_SIZE;
        );
    idx += 1;
);

// draw grid
set_color_panel_zero();
idx = 0; loop(9,
    unit = idx*RECT_SIZE;
    last = RECT_SIZE*PAD_COLS;
    gfx_x = unit; gfx_y = 0;
    end_x = unit; end_y = last;
    gfx_lineto(end_x, end_y);
    idx += 1;
);
idx = 0; loop(9,
    unit = idx*RECT_SIZE;
    last = RECT_SIZE*PAD_COLS;
    gfx_x = 0;    gfx_y = unit;
    end_x = last; end_y = unit;
    gfx_lineto(end_x, end_y);
    idx += 1;
);

```