pygame.midiでつくる仮想キーボード ①下準備〜MIDI入力の確認・信号の送信

# 本シリーズについて
このシリーズでは、pygame.midiによるMIDIの入出力を学びながら、最終的にカスタマイズできる仮想キーボードを作ります。筆者本人も初心者ではありますので、内容に間違い等ありましたらコメントで教えて下さい。
## 筆者の環境
ハード: MacbookPro 2018 (intel)
OS: macOS Big Sur 11.6
python関連: python 3.7.3、pygame 2.0.1

:::note warn
Windowsでも実行できるはずですが、筆者の環境はMacであるうえ、執筆にあたってWindowsでの動作の確認をしていません。
不具合等が生じるかもしれませんが、そのときはご自身で解決してください。
:::

## 当記事を書こうと思ったきっかけ
AutoTonicというソフトをご存知でしょうか。スケールを設定して白鍵だけで曲を弾けるという、とても便利で面白いソフトです(完全版は有料、機能制限版のAutoTonic Playerは無料)。
しかし、非常に残念なことに__Garagebandでは特定のMIDIデバイスだけを選択して録音することができません。__実際にAutoTonic Playerを使用したところ、元の物理MIDIキーボードの入力とAutoTonicによる仮想MIDIキーボードの入力が混じってしまい正常に使用できませんでした。
そこで、(MIDIでない、文字の)キーボード入力を検知してMIDI信号を出せば良いのではないか、という結論に至りました。今回はそれを作っていこうと思います。
# 下準備
## 1. pygameをインストールする
どこの記事でも書かれているので完結にしますが、ターミナル等で以下を実行してください。

```
pip install pygame
```

## 2. ループバック用のドライバを有効にする
### Macの場合
Audio MIDI設定アプリケーションを開きます。
![スクリーンショット 2021-10-03 10.19.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102419/bc5e48ef-f2a4-9d37-7610-3d138751ec13.png)
メニューバーからウィンドウ→MIDIスタジオを表示を押します。
![スクリーンショット 2021-10-03 10.21.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102419/1b2f9a50-70bf-17c1-8be8-8ff0a710d004.png)
「IACドライバ」(デフォルトの名前)をダブルクリックしてください。
![スクリーンショット 2021-10-03 10.23.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102419/205485a5-19a7-cc59-9676-dfc4f6c9aae7.png)
ここで、①名前を__任意の半角アルファベットの名前に変更__(ここでは「IAC Driver」)し、②__「装置はオンライン」__にチェックをつけて、③左下「ポート」の下の+を押して__任意の半角アルファベットの名前__で追加してください(ここでは「My Port」にします)。
![スクリーンショット 2021-10-03 10.32.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1102419/1aec7524-faad-d9ea-9263-37ebca49ffaf.png)

:::note warn
ここで、ドライバの名前を変更したのには理由があります。
日本語環境の場合、デフォルトで「IACドライバ」となっていますが、pygame.midiでドライバ情報を表示する際、全角文字が含まれているとドライバ名が空欄にされてしまいます。
それ以外の部分の動作に問題は生じませんが、MIDI出力ポートの選択時に面倒になるため名前を半角アルファベットで書いておくことを推奨します(ポート名も同様)。
:::

### Windowsの場合
「Windows Virtual MIDI Driver」などと検索して、フリーのMIDIドライバをインストールしてください。
virtualMIDIやloopMIDIなどが使用できるかと思います(未検証)。
そしてそのソフトウェア上で、任意の名前のポートを作成してください。



## 3. MIDIデバイス一覧を出力する
[pygame.midiの公式リファレンス](https://www.pygame.org/docs/ref/midi.html)によると、次のようにあります。
>pygame.midi.get_device_info()
returns information about a midi device
get_device_info(an_id) -> (interf, name, input, output, opened)
get_device_info(an_id) -> None
Gets the device info for a given id.

>Parameters:	an_id (int) -- id of the midi device being queried
Returns:	if the id is out of range None is returned, otherwise a tuple of (interf, name, input, output, opened) is returned.
interf: string describing the device interface (e.g. 'ALSA')
name: string name of the device (e.g. 'Midi Through Port-0')
input: 1 if the device is an input device, otherwise 0
output: 1 if the device is an output device, otherwise 0
opened: 1 if the device is opened, otherwise 0
Return type:	tuple or None

要するに、pygame.midi.get_device_info(id)で指定したidのデバイス情報を読み取れるようです。
[この記事](https://np2lkoo.hatenablog.com/entry/2016/08/31/024827)を参考に、実際に出力してみましょう。
__なお、pygame.midiといちいち打つのは面倒なので、以降mと書きます。__

```Python
import pygame.midi as m
def main():
    m.init()
    i_num = m.get_count()
    for i in range(i_num):
        print(i)
        print(m.get_device_info(i))
    m.quit()
    exit()

if __name__=="__main__":
    main()
```

microKEY2というキーボードを挿した状態で実行した結果、以下のようになりました。

```
pygame 2.0.1 (SDL 2.0.14, Python 3.7.3)
Hello from the pygame community. https://www.pygame.org/contribute.html
0
(b'CoreMIDI', b'microKEY2 KEYBOARD', 1, 0, 0)
1
(b'CoreMIDI', b'IAC Driver My Port', 1, 0, 0)
2
(b'CoreMIDI', b'microKEY2 CTRL', 0, 1, 0)
3
(b'CoreMIDI', b'IAC Driver My Port', 0, 1, 0)
```
しっかりとMIDIデバイスの一覧が表示されました。'IAC Driver My Port'が2つあるのはループバック用だからです。

## 信号のテスト送信
MIDIのノートの信号を送るには、m.Output.note_on()を使えばいいようです。
m.Output.note_on(ノート番号, ベロシティ)
で鍵盤を押した信号を出力できます(例として、ノート番号60はC3の音になります)。
_ちなみに、m.Output.write_short(0x90,ノート番号,ベロシティ)でも全く同じ動作になります。_
実際に送信してみましょう。

```Python
import pygame.midi as m
import time

def main():
    m.init()
    i_num = m.get_count()
    for i in range(i_num):
        print(i)
        print(m.get_device_info(i))

    midiout = m.Output(1) #直前のMIDIポート一覧から仮想デバイスのポート(自分の環境では「IAC Driver My Port」)のIDを確認して、その数値にしてください
    midiout.note_on(60,100)
    time.sleep(1)
    midiout.note_off(60)
    midiout.note_on(64,100)
    time.sleep(1)
    midiout.note_off(64)
    midiout.note_on(67,100)
    time.sleep(1)
    midiout.note_off(67)
    time.sleep(1)
    midiout.note_on(60,100)
    midiout.note_on(64,100)
    midiout.note_on(67,100)
    time.sleep(1)
    midiout.note_off(60,100)
    midiout.note_off(64,100)
    midiout.note_off(67,100)

    midiout.close()
    m.quit()
    exit()

if __name__=="__main__":
    main()
```
バックグラウンドでGaragebandを開き、ピアノのソフトウェア音源を選択した状態で実行してみると……しっかりと音が鳴っていることがわかります！

:::note
ちなみに、以下のスクリプトでノート番号→アルファベットの音階に変換できます。
↓
notename = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
print(notename[(ノート番号)%12] + str((ノート番号)//12-2))

例: 60→C3、75→D#4
:::

# 次回について
次回は、(文字の)キーボード入力やGUI上でのボタンからMIDI信号を出すというところから始めようと思います。