![見出し画像](https://assets.st-note.com/production/uploads/images/165689757/rectangle_large_type_2_3f8dd9192e91c799cb04a9cce43de788.jpeg?width=1200)
midoで3バイトを超えるコントロールチェンジをするハック
midoのバリデーションで、3バイトを超えるコントロールチェンジが扱えないようだ
一方、SAM2695では、3バイトを超えるコントロールチェンジを特殊メッセージとして受け付けるようだ
midoとpython-rtmidiを編集して、3バイトを超えるコントロールチェンジが送信できるようにしてみる
以下のつづき
環境
Windows
Python 3.12
mido 1.3.3
python-rtmidi 1.5.8
全体の流れ
python-rtmidiのバリデーションを緩くする
RtMidi(python-rtmidiに同梱)のコントロールチェンジ時のバッファ確保処理の変更
midoにおいて、bytes型(unsinged char型の配列)でデータを送信できるようなメソッドを追加
python-rtmidi
ソースの取得
以下のURLから、Source Distribution python_rtmidi-1.5.8.tar.gz をダウンロード
GitHubからcloneしてもよいと思う
https://pypi.org/project/python-rtmidi/
ファイルを展開
コマンドプロンプトなどで、展開したディレクトリを開く
バージョン番号の変更
meson.build を開いてバージョン番号を変更する
正式なリリースバージョンと勘違いように、特殊なバージョンであることわかるようにする
好きな文字列を指定したかったが、有効なバージョン名である必要があるようだ
https://packaging.python.org/en/latest/discussions/versioning/
世の中には、従ってないパッケージもあるので、回避方法があるかもしれない
以下のように、dev1を付与した
本当は、mod_by_me など付けたかったが…
version: '1.5.8.dev1'
python-rtmidi側のソースの編集
本来は、「src/_rtmidi.pyx」を編集して、Cythonで、C++のファイルに変換するのが、まっとうな手段だと思われるが、
「以下のようなコマンドで筆者がCythonによって変換したC++ファイル」と「変換済みのC++ファイル」で、400行くらい不一致が出た(Cythonのバージョンによるもの?)
cython -3 --cplus -o _rtmidi_mod.cpp _rtmidi.pyx
精査するのが手間なので、C++のコードを直接、編集してバリデーションを取り除く、という暴挙に出る
_rtmidi.cpp を開く
GitHubにはコミットされていなそう
以下のエラー時の文字列で、検索すればすぐに該当箇所がみつかる
Python(Cython)のコードがC++のコメントで残る、親切設計
'message' longer than 3 bytes but does not
正直、何をしているか、読めていないが、
バリデーションをして、エラー処理をしているであろうコードを、ごっそりコメントアウトしてみる
「空データ」と「0xF0からはじまらない3バイトを超えるメッセージ」をエラーとする処理
// __pyx_t_6 = __Pyx_PyObject_Call(__pyx_builtin_ValueError, __pyx_tuple__7, NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 1103, __pyx_L1_error)
// __Pyx_GOTREF(__pyx_t_6);
// __Pyx_Raise(__pyx_t_6, 0, 0, 0);
// __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
// __PYX_ERR(0, 1103, __pyx_L1_error)
Cythonで変換したC++ファイルにおいても、ビルドが成功し、動作も問題なさそうだった
_rtmidi.pyx を編集する場合は以下の部分
elif msg_v.size() > 3 and msg_v.at(0) != 0xF0:
raise ValueError("'message' longer than 3 bytes but does not "
"start with 0xF0.")
RtMidi側のソースの編集
RtMidi.cppを開き、void MidiOutWinMM :: sendMessage()メソッドを変更する(今回は、WindowsでWinMMのみが対象)
コントロールチェンジは、3バイト前提で、固定長の処理になっている
かなり強引ではあるが、システムエクスクルーシブのときの処理を流用してみる
エラーメッセージはシステムエクスクルーシブ前提になっているが…
MidiOutWinMM :: sendMessage()にある、以下のif文を変更して、3バイトを超えるコントロールチェンジでも可変長のメッセージを送信するようにする(変更前は、システムエクスクルーシブのときのみ可変長の処理になる)
変更前
if ( message[0] == 0xF0 ) { // Sysex message
変更後
if ( message[0] == 0xF0 || ((message[0] & 0xF0) == 0xB0 && nBytes > 3) ) { // Sysex message or Long Control Chnage
ちなみに、コントロールチェンジのときは、3バイトを超える場合はエラーになる処理が実装されている
Make sure the message size isn't too big. の部分
ビルドの準備
ビルドに必要なパッケージのインストール
pip install build installer
ビルド
ドキュメントでお勧めされている PEP 517 build method でやってみる
ビルドシステムは、Mesonのようなので、慣れている方は、Mesonも選択できる
以下のようにコマンドを実行すると何も問題なくビルドでき、
distフォルダに、whlファイルが生成された
python -m build --wheel
インストール
ドキュメントにある以下の方法だと
python -m installer dist/*.whl
PermissionErrorが出てしまう
おそらく、pyenvを使って環境を作っていなく、インストール先が正しくなく
ファイルを書き込めないと思われる
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python という存在しないフォルダに書き込もうとしているようだ
PermissionError: [WinError 5] アクセスが拒否されました。
pipでwhlファイルをインストールしてみると、インストールできた
pip install python_rtmidi-1.5.8.dev1-cp312-cp312-win_amd64.whl
インストールの確認
パッケージのリストを表示してみると、
無事に、dev1と付与された(3バイトを超えるコントロールチェンジができる) python-rtmidi がインストールされた
> pip list
Package Version
------------------ -----------
mido 1.3.3
python-rtmidi 1.5.8.dev1
mido
Pythonのライブラリフォルダを開く
WindowsアプリのPythonの場合、以下のようなパスになるはず
pyenvの場合は異なる
%userprofile%\AppData\Local\Packages\PythonSoftwareFoundation.Python{VERSION}.{ID}\LocalCache\local-packages\Python{VERSION}\site-packages\mido\backends\
rtmidi.py を開く
def send(self, msg) の後などに、def send_bytes(self, bytes) を追加する
message型ではなく、bytes型でメッセージを送信するメソッド
def send(self, msg):
"""Send a message on the port."""
with self._send_lock:
self._rt.send_message(msg.bytes())
def send_bytes(self, bytes):
"""Send bytes on the port."""
with self._send_lock:
self._rt.send_message(bytes)
実験
以下のmidoで音を出すコードで実験
コントロールチェンジ前は、通常の音色で
コントロールチェンジ後は、フィルタが適用された音になった
import mido
import time
port = mido.open_output("MIDI Output Poart name")
# reset
port.send(mido.Message.from_bytes([0xFF]))
# Before
msg = mido.Message('note_on', note=60)
port.send(msg)
time.sleep(1.0)
# cutoff frequency
port.send_bytes([0xb0, 0x63, 0x01, 0x62, 0x20, 0x06, 0x00])
# resonance
port.send_bytes([0xb0, 0x63, 0x01, 0x62, 0x21, 0x06, 0x00])
# After
msg = mido.Message('note_on', note=60)
port.send(msg)
time.sleep(1.0)
疑問点
以前の実験で、RtMidiを使った場合は、3バイトを超えるコントロールチェンジができた
しかし、今回、python rtmidi付属のRtMidiのコードにおいて、3バイトを超えるコントロールチェンジができないように実装されていた
現在最新のRtMidiのコードを読んでみる
if ( nBytes > 3 ) { // Sysex message
データ長が3バイトを超えると、可変長の処理がされるように変更されている
midoは最新とは異なるRtMidiを使っている(rtmidi @ f6ea40d)
mido用のブランチがあり、そちらは3バイトを超えるコントロールチェンジはできないようになっている
需要がないと(自分もそれほど求めていないと)感じつつも、ついつい時間をかけて調査してしまった
とはいえ、色々、学べることはあった(試行錯誤してゴールする達成感を感じた)
そして、RtMidiの実装が、3バイトを超えるコントロールチェンジを許容するように変更されていることを考えると、まったく需要がないというわけではないのかもしれない