PythonでMIDI


pygameのインストール

PythonでMIDIを扱うために、MIDI機器からのMIDIイベントを逐次的にモニターできるものを探していました。 pygameというモジュールが使えそうなので試してみることにします。

私の環境はWindows 10 Home, Python 3.8.6です。Pythonはwww.python.orgからダウンロードしました。pygameは以下のコマンドでインストールできます。


> pip install pygame

余談ですが、pipインストールで以下のようなSSLのエラーに遭遇しました。いろいろ試したのですがpythonをインストールし直すことでしか解決できませんでした。

ERROR: No matching distribution found for pprint
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming 
the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries
exceeded with url: /simple/pip/ (Caused by SSLError(SSLCertVerificationError
(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get
local issuer certificate (_ssl.c:1108)'))) - skipping

pygameのドキュメントは、www.pygame.orgにあります。以下に関数の簡単な説明を書いておきます。

  • pygame.midi
        MIDI入力および出力を扱うpygameモジュール。
  • pygame.midi.init
        MIDIモジュールの初期化
  • pygame.midi.quit
        MIDIモジュールを解除
  • pygame.midi.get_init
        MIDIモジュールの初期化をチェック。初期化されている場合はTrueを返す
  • pygame.midi.Input
        入力は、midiデバイスからmidi入力を取得
  • pygame.midi.Output
        出力は、MIDIを出力デバイスに送信
  • pygame.midi.get_count
        接続されているMIDIデバイスの数を取得
  • pygame.midi.get_default_input_id
        デフォルトの入力デバイス番号を取得
  • pygame.midi.get_default_output_id
        デフォルトの出力デバイス番号を取得します
  • pygame.midi.get_device_info
        MIDIデバイスの情報を返す
            戻り値 (interf、name、input、output、opened)
            interf:デバイスインターフェイスの説明 MMSystem:Windows
            name:デバイス名
            input: 入力デバイスは 1
            output: 出力デバイスは 1)
            opened: デバイスが開いている時 1
        詳細は次の例を参照のこと
  • pygame.midi.midis2events
        MIDIイベントをpygameイベントに変換
  • pygame.midi.time
        PortMidiタイマーの現在の時刻をミリ秒単位で取得
  • pygame.midi.frequency_to_midi
        周波数をMIDIノートに変換
  • pygame.midi.midi_to_frequency
        MIDIノートを周波数に変換
  • pygame.midi.midi_to_ansi_note
        MIDI番号のANSIで音階名を返す


デバイスの情報表示

以下にサンプルプログラムを掲載します。サンプルプログラムではpygame.midiを"m"の一文字で読み込んでいます。

import pygame.midi as m

m.init()            # MIDIデバイスを初期化
i_num=m.get_count() # MIDIデバイスの数
for i in range(i_num):
	print(m.get_device_info(i)) # MIDIデバイスの情報を表示

[結果]
pygame 2.0.0 (SDL 2.0.12, python 3.8.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
(b'MMSystem', b'Microsoft MIDI Mapper', 0, 1, 0) // 入力
(b'MMSystem', b'loopMIDI Port', 1, 0, 0)         // 入力
(b'MMSystem', b'nanoKEY2', 1, 0, 0)              // 入力
(b'MMSystem', b'Microsoft GS Wavetable Synth', 0, 1, 0) // 出力
(b'MMSystem', b'loopMIDI Port', 0, 1, 0)                // 出力
(b'MMSystem', b'nanoKEY2', 0, 1, 0)                     // 出力

MIDIの受信

import pygame.midi as m

m.init()
i = m.Input(2) # デバイス番号を2('nanoKEY2')とする
while True:
  if i.poll(): # MIDIが受信されると1
    midi_events = i.read(1) # 読み取る入力イベントの数 
    print ("midi_events:" + str(midi_events))


[結果]
pygame 2.0.0 (SDL 2.0.12, python 3.8.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
midi_events:[[[144, 48, 55, 0], 1396]]
midi_events:[[[128, 48, 64, 0], 1644]]
midi_events:[[[144, 48, 54, 0], 2331]]
midi_events:[[[128, 48, 64, 0], 2495]]
...
...

上のプログラムでlistタイプのmidi_eventsを、numpy arrayに変換して、 さらにMIDIのイベントを16進で表示するプログラムを書いて見ます。MIDIを扱う人には16進の方がわかりやすいですね。

import pygame.midi as m
import numpy as np

m.init()
i = m.Input(2) # デバイス番号を2('nanoKEY2')とする
while True:
  if i.poll(): # MIDIが受信されると1
    midi_events = i.read(1) # 読み取る入力イベントの数 
    dd=np.array(midi_events, dtype=object) #listを配列に変換
    status = dd[0][0][0]
    data1 = dd[0][0][1]
    data2 = dd[0][0][2]    
    print("%02x" %status,"%02x" %data1, "%02x" %data2)

[結果]
90 37 37
80 37 40
90 37 7f
80 37 40
90 37 76
80 37 40
...
...

System Exclusiveの受信
import pygame.midi as m

m.init()
i = m.Input(2) # デバイス番号を2('nanoKEY2')とする
while True:
  if i.poll(): # MIDIが受信されると1
    midi_events = i.read(4) # 読み取る入力イベントの数を4とする
    print ("midi_events:" + str(midi_events))

このプログラムで、以下の18バイトのSystem Exclusiveを受信してみます。

0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF7

読み取る入力イベントの数を4と設定しているので、1回目は0xF0から0x0Eまでのは4x4の16バイト、2回目に0x0Fと0xF7の2バイトの2回に分けて受信します。

//1回目の受信
midi_events:[
    [[240, 0, 1, 2], 41131],
    [[3, 4, 5, 6], 41131],
    [[7, 8, 9, 10], 41131], 
    [[11, 12, 13, 14], 41131]
]

//2回目の受信
midi_events:[
    [[15, 247, 0, 0], 41131]
]

MIDIを受信し、ソフトシンセへ送信

import pygame.midi as m
m.init()
i = m.Input(2)
o = m.Output(0)

while True:
  if i.poll(): # MIDIが受信されると1
    midi_events = i.read(4)
    for j in range(len(midi_events)):
      status=midi_events[j][0][0]
      data1 =midi_events[j][0][1]
      data2 =midi_events[j][0][2]
       if(status==0x80 or status==0x90): #Note OnかOFFならMIDI送信
        o.write_short(status,data1,data2)
i.close()

MIDIの音階情報

import pygame.midi as m

for note in range(128):
	print(note," : ",end="")
	print(m.midi_to_ansi_note(note)," : ",end="")
	print(m.midi_to_frequency(note), " Hz")


[結果]
...
60  : C4  : 261.6  Hz
61  : C#4  : 277.2  Hz
62  : D4  : 293.7  Hz
63  : D#4  : 311.1  Hz
64  : E4  : 329.6  Hz
65  : F4  : 349.2  Hz
66  : F#4  : 370.0  Hz
67  : G4  : 392.0  Hz
68  : G#4  : 415.3  Hz
69  : A4  : 440.0  Hz
70  : A#4  : 466.2  Hz
71  : B4  : 493.9  Hz
72  : C5  : 523.3  Hz
...

Raspberry Piの場合

2025/09/14

Raspberry Piでpygameを試して見ました。Raspberry Piのシステムのバージョンは以下の通りです。

pi@raspi3:~ $ uname -r
6.12.34+rpt-rpi-2712
pi@raspi3:~ cat /proc/version
Linux version 6.12.34+rpt-rpi-2712 (serge@raspberrypi.com) 
 (aarch64-linux-gnu-gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0, 
 GNU ld (GNU Binutils for Debian) 2.40) 
 #1 SMP PREEMPT Debian 1:6.12.34-1+rpt1~bookworm (2025-06-26)

また、Pythonのバージョンは以下の通りです。

pi@raspi3:~ $ python -V
Python 3.11.2

pygameをpip installでインストールしようとすると以下のエラーがでました。

pi@raspi3:~ $ sudo pip install pygame
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

書いてある通り、apt install python3-xyzでインストールすることができました。

pi@raspi3:~ $ sudo apt install python3-pygame

デバイスを列記してみると、以下のように表示されました。

[プログラム]
import pygame.midi as m
m.init()                           # MIDIデバイスを初期化
i_num=m.get_count()                # MIDIデバイスの数
for i in range(i_num):
    print(i, m.get_device_info(i)) # MIDIデバイスの情報を表示

[結果]
pygame 2.1.2 (SDL 2.26.5, Python 3.11.2)
Hello from the pygame community. https://www.pygame.org/contribute.html
0 (b'ALSA', b'Midi Through Port-0', 0, 1, 0)  // 出力
1 (b'ALSA', b'Midi Through Port-0', 1, 0, 0)  // 入力
2 (b'ALSA', b'nanoKEY2 _ CTRL', 0, 1, 0)      // 出力
3 (b'ALSA', b'nanoKEY2 _ CTRL', 1, 0, 0)      // 入力

Midi Through Portを使ったSys Exの送受信

Midi Through PortはALSA上のMIDIのバーチャルケーブルです。 2つのアプリケーションの間をMIDIでつなぎます。デバイス情報のリストを表示した時、次のようにバーチャルケーブルが表示されました。

0 (b'ALSA', b'Midi Through Port-0', 0, 1, 0)  // 出力
1 (b'ALSA', b'Midi Through Port-0', 1, 0, 0)  // 入力

デバイスの0が出力用、アプリケーション1がデバイス0に対してMIDIを出力します。 デバイスの1が入力用、アプリケーション2はデバイスの1からMIDIを受信します。 すなわち、アプリケーション1からアプリケーション2に対してMIDIを送ったことになります。

一つのターミナルでSystem Exclusiveの受信アプリケーションを先に起動し、もう一つのターミナルでSystem Exclusiveの送信 アプリケーションを動作させます。


#ReceiveSysEx.py
#System Exclusiveの受信
import pygame.midi as m

m.init()
i = m.Input(1) # 入力デバイス番号を1とする
while True:
  if i.poll():
    midi_events = i.read(1)
    dd=np.array(midi_events, dtype=object)
    d1 = dd[0][0][0]
    d2 = dd[0][0][1]
    d3 = dd[0][0][2] 
    d4 = dd[0][0][3]   
    print("%02x" %d1,"%02x" %d2, "%02x" %d3, "%02x" %d4)

#SendSysEx.py
#System Exclusiveの送信

import pygame.midi as m
m.init()
o = m.Output(0)
sysex=[0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF7]
o.write_sys_ex(m.time(),sysex)

[結果]
f0 00 01 02
03 04 05 06
07 08 09 0a
0b 0c 0d 0e
0f f7 00 00