Jetson Orinで外部USB-MIDI音源を使う簡単な方法
Jetson Orinで色々実験しています。Jetson Orinの概要とセットアップに関しては以下記事参照ください。
今回は、MIDI再生を試そうとしたところハマったりしたので記事を書きます。外部MIDI音源(具体的にはUSB-MIDI EDIROL UM-1 EX経由でKANTAN Play(かんぷれ))を再生しようとしたら以下のようなエラーに遭遇しました。
_rtmidi.SystemError: MidiInAlsa::initialize: error creating ALSA sequencer client object.
以前Jetson NnanoでMIDIを使おうとしたときに、カーネルビルドをした悲しい過去があるので「またカーネルビルドが必要なのかな?」と思いつつ調べていると、(主に生成AIの力で)外部USB-MIDI音源なら簡単に使えることが判明しました。
MIDI接続の2つの方法と従来の問題
MIDIデバイスを鳴らす場合、通常ALSAシーケンサーを使います。今回は、ALSAシーケンサを使わず、直接アクセスする方法をとっています。
Pythonアプリ → ALSAシーケンサー → MIDIデバイス
よくあるエラーの正体
JetsonでMIDI関連のPythonライブラリ(rtmidi、mido等)を使おうとすると、以下のエラーが発生します:
ALSA lib seq_hw.c:466:(snd_seq_hw_open) open /dev/snd/seq failed: No such file or directory
_rtmidi.SystemError: MidiInAlsa::initialize: error creating ALSA sequencer client object.
このエラーの原因:
-
rtmidi
やmido
はデフォルトでALSAシーケンサーを使おうとする - でもJetsonには
snd-seq
モジュール(ALSAシーケンサー)が入ってない - だから「ALSAシーケンサーが見つからない」エラーが出る
Jetsonの現状確認
# シーケンサーモジュールを確認
$ lsmod | grep seq
nvvrs_pseq_rtc 16384 0 # ←NVIDIA関連のみ
nvidia_vrs_pseq 16384 0
# /dev/snd/seq デバイスも存在しない
$ ls -la /dev/snd/seq
ls: cannot access '/dev/snd/seq': No such file or directory
確かに、MIDI用のシーケンサーモジュールは入っていません。
解決策:直接アクセス
USB-MIDIデバイスの接続確認
USB-MIDIインターフェースを接続すると、ファイルとして認識されます:
# USB接続確認
$ lsusb
Bus 001 Device 008: ID 0582:009d Roland Corp. EDIROL UM-1 # ←認識されている
# ALSAカードとして登録されているか確認
$ cat /proc/asound/cards
0 [UM1 ]: USB-Audio - UM-1
EDIROL UM-1 at usb-3610000.usb-2.2, full speed
# MIDIデバイスファイルの確認
$ ls -la /dev/snd/ | grep midi
crw-rw----+ 1 root audio 116, 2 6月 15 12:02 midiC0D0 # ←重要!
/dev/snd/midiC0D0
が作成されていれば、準備完了です。
MacとJetsonの両方に対応したスクリプト
MIDIデバイスファイルを直接指定する以下のようなコードで音を出すことができました。
"""
MCPサーバーを使わずに直接MIDI信号を連続送信するテスト
Mac (mido) と Jetson (raw MIDI) の両方に対応
"""
import time
import platform
import os
class UniversalMIDI:
def __init__(self):
self.is_jetson = self._detect_jetson()
self.midi_device = None
def _detect_jetson(self):
"""Jetson環境かどうかを判定"""
return platform.machine().startswith("aarch64") and os.path.exists("/dev/snd/midiC0D0")
def get_output_names(self):
"""利用可能なMIDI出力ポート名を取得"""
if self.is_jetson:
if os.path.exists("/dev/snd/midiC0D0"):
return ["/dev/snd/midiC0D0 (UM-1)"]
else:
return []
else:
import mido
return mido.get_output_names()
def open_output(self, port_name):
"""MIDI出力ポートを開く"""
if self.is_jetson:
self.midi_device = open("/dev/snd/midiC0D0", "wb")
return self
else:
import mido
self.midi_device = mido.open_output(port_name)
return self.midi_device
def send_message(self, msg_type, channel=0, note=None, velocity=None):
"""MIDIメッセージを送信"""
if self.is_jetson:
if msg_type == "note_on":
midi_bytes = bytes([0x90 + channel, note, velocity])
elif msg_type == "note_off":
midi_bytes = bytes([0x80 + channel, note, velocity])
else:
return
self.midi_device.write(midi_bytes)
self.midi_device.flush()
else:
import mido
msg = mido.Message(msg_type, channel=channel, note=note, velocity=velocity)
self.midi_device.send(msg)
def close(self):
"""ポートを閉じる"""
if self.midi_device:
self.midi_device.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def test_direct_midi_rapid_fire():
"""遅延なしでMIDI信号を連続送信"""
try:
midi = UniversalMIDI()
# 利用可能な出力ポートを確認
output_names = midi.get_output_names()
print(f"利用可能なMIDI出力ポート: {output_names}")
print(f"環境: {'Jetson' if midi.is_jetson else 'Mac/PC'}")
if not output_names:
print("MIDI出力ポートが見つかりません")
return
# 最初のポートを使用
port_name = output_names[0]
print(f"使用するポート: {port_name}")
with midi.open_output(port_name):
# テストシーケンス: C4, D4, E4, F4, G4
notes = [60, 62, 64, 65, 67]
print("\n=== 遅延なし連続送信テスト ===")
for note in notes:
# Note On
if midi.is_jetson:
midi.send_message("note_on", channel=0, note=note, velocity=64)
else:
midi.send_message("note_on", channel=0, note=note, velocity=64)
print(f"Note {note} ON送信")
# 極短時間待機(ハードウェアの処理時間)
time.sleep(0.01)
# Note Off
if midi.is_jetson:
midi.send_message("note_off", channel=0, note=note, velocity=0)
else:
midi.send_message("note_off", channel=0, note=note, velocity=0)
print(f"Note {note} OFF送信")
# 次のノートまでの最小間隔
time.sleep(0.05)
print("\n=== 高速BPMシミュレーション (BPM 240) ===")
# BPM 240 = 250ms/beat
beat_duration = 60.0 / 240
note_duration = beat_duration * 0.3 # 75ms
note_gap = beat_duration * 0.1 # 25ms
print(f"ノート長: {note_duration * 1000:.1f}ms, 間隔: {note_gap * 1000:.1f}ms")
for note in notes:
if midi.is_jetson:
midi.send_message("note_on", channel=0, note=note, velocity=64)
else:
midi.send_message("note_on", channel=0, note=note, velocity=64)
print(f"Note {note} ON (高速)")
time.sleep(note_duration)
if midi.is_jetson:
midi.send_message("note_off", channel=0, note=note, velocity=0)
else:
midi.send_message("note_off", channel=0, note=note, velocity=0)
time.sleep(note_gap)
print("\n=== 同時和音テスト ===")
chord = [60, 64, 67] # Cメジャー
# 和音ON
for note in chord:
if midi.is_jetson:
midi.send_message("note_on", channel=0, note=note, velocity=64)
else:
midi.send_message("note_on", channel=0, note=note, velocity=64)
print(f"Chord note {note} ON")
time.sleep(1.0) # 1秒間和音を鳴らす
# 和音OFF
for note in chord:
if midi.is_jetson:
midi.send_message("note_off", channel=0, note=note, velocity=0)
else:
midi.send_message("note_off", channel=0, note=note, velocity=0)
print(f"Chord note {note} OFF")
print("\nテスト完了")
except Exception as e:
print(f"エラー: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
test_direct_midi_rapid_fire()
実行結果
実行結果です。
$ python3 test_direct_midi.py
利用可能なMIDI出力ポート: ['UM-1']
環境: Mac/PC
使用するポート: UM-1
=== 遅延なし連続送信テスト ===
Note 60 ON送信
Note 60 OFF送信
Note 62 ON送信
Note 62 OFF送信
Note 64 ON送信
Note 64 OFF送信
Note 65 ON送信
Note 65 OFF送信
Note 67 ON送信
Note 67 OFF送信
=== 高速BPMシミュレーション (BPM 240) ===
ノート長: 75.0ms, 間隔: 25.0ms
Note 60 ON (高速)
Note 62 ON (高速)
Note 64 ON (高速)
Note 65 ON (高速)
Note 67 ON (高速)
=== 同時和音テスト ===
Chord note 60 ON
Chord note 64 ON
Chord note 67 ON
Chord note 60 OFF
Chord note 64 OFF
Chord note 67 OFF
音が正常に出力されます!
MIDIデバイスファイル名の固定
このままだとUSB-MIDIデバイス、USBを接続するたびに異なるMIDIデバイスファイル名になるので不便です。udev使うことで固定できます。
以下はrulesファイルの例です。
To Do(後で追記します)。
まとめ
Jetson Orinで外部USB-MIDI音源を使用する方法について紹介しました。Claudeには、わざわざカーネルビルドをするのは完全にアホと罵られましたが、MIDIデバイスが増えていくと、直接指定は結構大変なので、思い切ってカーネルビルドもよいのではないかなと思ったりしています。
この記事がJetsonでMIDI使いたい人の参考になれば幸いです。
余談
AIに完全にアホよばわりされながら、試行錯誤してました。
参考リンク
- USB-MIDIクラス仕様: https://www.usb.org/sites/default/files/midi10.pdf
- ALSA Documentation: https://www.alsa-project.org/wiki/Documentation
関連記事
Discussion