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を"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) // 出力
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 ... ...
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] ]
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()
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 ...
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は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